diff --git a/.gitignore b/.gitignore index 17a60fc..8c82aed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ __pycache__/ **/__pycache__/ -datasets/ models/ *.pth +/datasets/ +!*/foo/ +examples/datasets/.~lock* \ No newline at end of file diff --git a/__init__.py b/__init__.py index baeddf4..8d8af09 100644 --- a/__init__.py +++ b/__init__.py @@ -67,6 +67,7 @@ from .modules.pandas_wrapper.pandas_kurtosis import PandasKurtosis from .modules.pandas_wrapper.pandas_load_csv import PandasLoadCSV from .modules.pandas_wrapper.pandas_load_csv_with_encoding import PandasLoadCSVWithEncoding from .modules.pandas_wrapper.pandas_load_csv_with_index import PandasLoadCSVWithIndex +from .modules.pandas_wrapper.pandas_load_excel import PandasLoadExcel from .modules.pandas_wrapper.pandas_load_html import PandasLoadHTML from .modules.pandas_wrapper.pandas_load_json import PandasLoadJSON from .modules.pandas_wrapper.pandas_loc_cell_str import PandasLocCellStr @@ -300,6 +301,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "PandasLoadCSV": PandasLoadCSV, "PandasLoadCSVWithEncoding": PandasLoadCSVWithEncoding, "PandasLoadCSVWithIndex": PandasLoadCSVWithIndex, + "PandasLoadExcel": PandasLoadExcel, "PandasLoadHTML": PandasLoadHTML, "PandasLoadJSON": PandasLoadJSON, "PandasLocCellStr": PandasLocCellStr, @@ -536,6 +538,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "PandasLoadCSV": "Pandas Load CSV", "PandasLoadCSVWithEncoding": "Pandas Load CSV With Encoding", "PandasLoadCSVWithIndex": "Pandas Load CSV With Index", + "PandasLoadExcel": "Pandas Load Excel", "PandasLoadHTML": "Pandas Load HTML", "PandasLoadJSON": "Pandas Load JSON", "PandasLocCellStr": "Pandas Loc Cell Str", diff --git a/docs/reference/node_reference.md b/docs/reference/node_reference.md index 1623b38..6bc1a96 100644 --- a/docs/reference/node_reference.md +++ b/docs/reference/node_reference.md @@ -109,6 +109,7 @@ | [Pandas Load CSV](pandas_load_csv.md) | Loads CSV files into a pandas DataFrame. | | [Pandas Load CSV With Encoding](pandas_load_csv_with_encoding.md) | Loads CSV files into a pandas DataFrame. | | [Pandas Load CSV With Index](pandas_load_csv_with_index.md) | Loads CSV files into a pandas DataFrame. | +| [Pandas Load Excel](pandas_load_excel.md) | Loads Excel files into a pandas DataFrame. | | [Pandas Load HTML](pandas_load_html.md) | Scans an HTML file or webpage for tables and returns the number of DataFrames | | [Pandas Load JSON](pandas_load_json.md) | Loads JSON files into a pandas DataFrame. | | [Pandas Save CSV](pandas_save_csv.md) | Saves a PandasDataFrame to a CSV file. | diff --git a/docs/reference/pandas_load_excel.md b/docs/reference/pandas_load_excel.md new file mode 100644 index 0000000..1371ee6 --- /dev/null +++ b/docs/reference/pandas_load_excel.md @@ -0,0 +1,23 @@ +# Pandas Load Excel +Loads Excel files into a pandas DataFrame. + +This node uses read_excel method. +See [Pandas documentation](https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html) for details. + +If sheet_name is not specified, first sheet will be read. + +## Input +| Name | Data type | +|---|---| +| file_path | String | +| sheet_name | String | + +## Output +| Data type | +|---| +| DataFrame | + +
+Category: IO + +ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved. diff --git a/examples/datasets/test_file.xlsx b/examples/datasets/test_file.xlsx new file mode 100644 index 0000000..d0466d8 Binary files /dev/null and b/examples/datasets/test_file.xlsx differ diff --git a/modules/pandas_wrapper/pandas_load_excel.py b/modules/pandas_wrapper/pandas_load_excel.py new file mode 100644 index 0000000..00f92b0 --- /dev/null +++ b/modules/pandas_wrapper/pandas_load_excel.py @@ -0,0 +1,55 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd + + +class PandasLoadExcel: + """ + Loads Excel files into a pandas DataFrame. + + This node uses read_excel method. + See [Pandas documentation](https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html) for details. + + If sheet_name is not specified, first sheet will be read. + + category: IO + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the `load_csv` function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "file_path": ("STRING", {"multiline": False}), + "sheet_name": ("STRING", {"default": ""}), + + } + } + + RETURN_TYPES: tuple = ("DATAFRAME",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + @classmethod + def IS_CHANGED(cls, **kw): + return float("NaN") + + def f(self, file_path: str, sheet_name: str) -> tuple: + """ + Loads an Excel file into a pandas DataFrame. + + Args: + file_path (str): The path to the Excel file. + + Returns: + tuple: A tuple containing a DataFrame. + """ + if len(sheet_name) == 0: + sheet_name = 0 # Read first sheet + df = pd.read_excel(file_path, sheet_name=sheet_name) + return (df,) diff --git a/pyproject.toml b/pyproject.toml index 50e24d1..bd99782 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "data-analysis" description = "Data analysis custom modules for ComfyUI – Integrates Pandas, Seaborn and Matplotlib into ComfyUI, featuring extensive PyTorch wrapper nodes for seamless tensor operations. Experimental PyTorch model training was added in v2.0." -version = "2.0.4" +version = "2.0.5" license = {file = "LICENSE"} [project.urls] diff --git a/requirements.txt b/requirements.txt index 58e2f64..b504a51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ matplotlib>=3.10.0 pandas>=2.2.3 seaborn>=0.13.2 scipy>=1.14.1 -scikit-learn>=1.6.1 \ No newline at end of file +scikit-learn>=1.6.1 +openpyxl>=3.1.5 \ No newline at end of file diff --git a/tests/pandas_wrapper/pandas_load_excel_test.py b/tests/pandas_wrapper/pandas_load_excel_test.py new file mode 100644 index 0000000..fd481db --- /dev/null +++ b/tests/pandas_wrapper/pandas_load_excel_test.py @@ -0,0 +1,64 @@ +import os +import sys +import unittest +import pandas as pd + +PROJECT_ROOT = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..")) +MODULE_ROOT = os.path.join(PROJECT_ROOT, "modules") +sys.path.append(MODULE_ROOT) +from pandas_wrapper.pandas_load_excel import PandasLoadExcel + + +class TestPandasLoadExcel(unittest.TestCase): + def setUp(self): + """Set up test data.""" + self.node = PandasLoadExcel() + self.test_file_path = os.path.join( + os.path.dirname(__file__), "..", "..", + "examples", "datasets", "test_file.xlsx") + + self.test_data_list = [ + ( + "Sheet1", + pd.DataFrame( + { + "Item": ["Bat", "Racquet", "Glove", "Hockey stick", "Soccer ball"], + "Qty": [3, 4, 5, 6, 7] + }, + index = [0, 1, 2, 3, 4] + ), + ), + ( + "FruitPrice", + pd.DataFrame( + { + "Fruit": ["Apple", "Banana", "Coconut", "Donut"], + "Price": [1.23, 2.34, 3.45, 4.56] + }, + index = [0, 1, 2, 3] + ) + ), + ( + "", # Leaving the sheet name blank should read the first sheet. + pd.DataFrame( + { + "Item": ["Bat", "Racquet", "Glove", "Hockey stick", "Soccer ball"], + "Qty": [3, 4, 5, 6, 7] + }, + index = [0, 1, 2, 3, 4] + ), + ), + ] + def test_read_excel(self): + """Reads multiple sheets from an excel file. + """ + for test_data in self.test_data_list: + with self.subTest(test_data=test_data): + sheet_name = test_data[0] + expected = test_data[1] + df = self.node.f(self.test_file_path, sheet_name=sheet_name)[0] + pd.testing.assert_frame_equal(df, expected) + + +if __name__ == "__main__": + unittest.main()