From d3bdcace9ef6af56cc5c46d255387e6687556ef7 Mon Sep 17 00:00:00 2001 From: HowToSD Date: Mon, 17 Feb 2025 01:49:46 -0800 Subject: [PATCH] Added Pandas Count, Max, Mean, Median, Min, Mode, Std, Sum, Var, Show Series nodes. --- __init__.py | 31 ++++++++++ docs/ug.md | 44 +++++++++++++- examples/workflows/summary_statistics.json | 1 + modules/pandas_wrapper/pandas_count.py | 42 +++++++++++++ modules/pandas_wrapper/pandas_max.py | 42 +++++++++++++ modules/pandas_wrapper/pandas_mean.py | 42 +++++++++++++ modules/pandas_wrapper/pandas_median.py | 42 +++++++++++++ modules/pandas_wrapper/pandas_min.py | 42 +++++++++++++ modules/pandas_wrapper/pandas_mode.py | 41 +++++++++++++ modules/pandas_wrapper/pandas_show_series.py | 56 +++++++++++++++++ modules/pandas_wrapper/pandas_std.py | 42 +++++++++++++ modules/pandas_wrapper/pandas_sum.py | 42 +++++++++++++ modules/pandas_wrapper/pandas_var.py | 42 +++++++++++++ pyproject.toml | 2 +- web/js/ShowSeries.js | 64 ++++++++++++++++++++ 15 files changed, 573 insertions(+), 2 deletions(-) create mode 100644 examples/workflows/summary_statistics.json create mode 100644 modules/pandas_wrapper/pandas_count.py create mode 100644 modules/pandas_wrapper/pandas_max.py create mode 100644 modules/pandas_wrapper/pandas_mean.py create mode 100644 modules/pandas_wrapper/pandas_median.py create mode 100644 modules/pandas_wrapper/pandas_min.py create mode 100644 modules/pandas_wrapper/pandas_mode.py create mode 100644 modules/pandas_wrapper/pandas_show_series.py create mode 100644 modules/pandas_wrapper/pandas_std.py create mode 100644 modules/pandas_wrapper/pandas_sum.py create mode 100644 modules/pandas_wrapper/pandas_var.py create mode 100644 web/js/ShowSeries.js diff --git a/__init__.py b/__init__.py index 7934a6e..d19274f 100644 --- a/__init__.py +++ b/__init__.py @@ -4,6 +4,7 @@ from .modules.matplotlib.mpl_bar import MPLBar from .modules.matplotlib.mpl_line import MPLLine from .modules.matplotlib.mpl_scatter import MPLScatter from .modules.pandas_wrapper.pandas_columns import PandasColumns +from .modules.pandas_wrapper.pandas_count import PandasCount from .modules.pandas_wrapper.pandas_create import PandasCreate from .modules.pandas_wrapper.pandas_create_with_index import PandasCreateWithIndex from .modules.pandas_wrapper.pandas_dropna import PandasDropNA @@ -20,16 +21,26 @@ from .modules.pandas_wrapper.pandas_load_csv_with_index import PandasLoadCSVWith from .modules.pandas_wrapper.pandas_load_json import PandasLoadJSON from .modules.pandas_wrapper.pandas_loc_cell_str import PandasLocCellStr from .modules.pandas_wrapper.pandas_loc_row_series import PandasLocRowSeries +from .modules.pandas_wrapper.pandas_max import PandasMax +from .modules.pandas_wrapper.pandas_mean import PandasMean +from .modules.pandas_wrapper.pandas_median import PandasMedian +from .modules.pandas_wrapper.pandas_min import PandasMin +from .modules.pandas_wrapper.pandas_mode import PandasMode from .modules.pandas_wrapper.pandas_save_csv import PandasSaveCSV from .modules.pandas_wrapper.pandas_save_json import PandasSaveJSON from .modules.pandas_wrapper.pandas_select_columns import PandasSelectColumns from .modules.pandas_wrapper.pandas_select_rows import PandasSelectRows from .modules.pandas_wrapper.pandas_series_to_string import PandasSeriesToString from .modules.pandas_wrapper.pandas_show_dataframe import PandasShowDataFrame +from .modules.pandas_wrapper.pandas_show_series import PandasShowSeries from .modules.pandas_wrapper.pandas_show_text import PandasShowText from .modules.pandas_wrapper.pandas_sort import PandasSort +from .modules.pandas_wrapper.pandas_std import PandasStd +from .modules.pandas_wrapper.pandas_sum import PandasSum from .modules.pandas_wrapper.pandas_summary import PandasSummary from .modules.pandas_wrapper.pandas_to_string import PandasToString +from .modules.pandas_wrapper.pandas_var import PandasVar + T = TypeVar("T") @@ -39,6 +50,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "MPLLine": MPLLine, "MPLScatter": MPLScatter, "PandasColumns": PandasColumns, + "PandasCount": PandasCount, "PandasCreate": PandasCreate, "PandasCreateWithIndex": PandasCreateWithIndex, "PandasDropNA": PandasDropNA, @@ -55,16 +67,25 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = { "PandasLoadJSON": PandasLoadJSON, "PandasLocCellStr": PandasLocCellStr, "PandasLocRowSeries": PandasLocRowSeries, + "PandasMax": PandasMax, + "PandasMean": PandasMean, + "PandasMedian": PandasMedian, + "PandasMin": PandasMin, + "PandasMode": PandasMode, "PandasSaveCSV": PandasSaveCSV, "PandasSaveJSON": PandasSaveJSON, "PandasSelectColumns": PandasSelectColumns, "PandasSelectRows": PandasSelectRows, "PandasSeriesToString": PandasSeriesToString, "PandasShowDataFrame": PandasShowDataFrame, + "PandasShowSeries": PandasShowSeries, "PandasShowText": PandasShowText, "PandasSort": PandasSort, + "PandasStd": PandasStd, + "PandasSum": PandasSum, "PandasSummary": PandasSummary, "PandasToString": PandasToString, + "PandasVar": PandasVar } """ NODE_CLASS_MAPPINGS (Dict[str, Type[T]]): @@ -77,6 +98,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "MPLLine": "MPL Line Plot", "MPLScatter": "MPL Scatter Plot", "PandasColumns": "Pandas Columns", + "PandasCount": "Pandas Count", "PandasCreate": "Pandas Create", "PandasCreateWithIndex": "Pandas Create With Index", "PandasDropNA": "Pandas Drop NA", @@ -93,16 +115,25 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = { "PandasLoadJSON": "Pandas Load JSON", "PandasLocCellStr": "Pandas Loc Cell Str", "PandasLocRowSeries": "Pandas Loc Row Series", + "PandasMax": "Pandas Max", + "PandasMean": "Pandas Mean", + "PandasMedian": "Pandas Median", + "PandasMin": "Pandas Min", + "PandasMode": "Pandas Mode", "PandasSaveCSV": "Pandas Save CSV", "PandasSaveJSON": "Pandas Save JSON", "PandasSelectColumns": "Pandas Select Columns", "PandasSelectRows": "Pandas Select Rows", "PandasSeriesToString": "Pandas Series To String", "PandasShowDataFrame": "Pandas Show DataFrame", + "PandasShowSeries": "Pandas Show Series", "PandasShowText": "Pandas Show Text", "PandasSort": "Pandas Sort", + "PandasStd": "Pandas Std", + "PandasSum": "Pandas Sum", "PandasSummary": "Pandas Summary", "PandasToString": "Pandas To String", + "PandasVar": "Pandas Var", } """ NODE_DISPLAY_NAME_MAPPINGS (Dict[str, str]): diff --git a/docs/ug.md b/docs/ug.md index 8b0ab9e..49a8edc 100644 --- a/docs/ug.md +++ b/docs/ug.md @@ -49,6 +49,7 @@ You can right-click, select **Add Node**, go to **Data Analysis**, and look for | Node Name | Functionality | |-------------------------|-------------------------------------| | **Pandas Show DataFrame** | Display DataFrame contents | +| **Pandas Show Series** | Display Series contents | | **Pandas Show Text** | Display Text | | **Pandas Summary** | Extract DataFrame statistics | @@ -58,6 +59,19 @@ You can right-click, select **Add Node**, go to **Data Analysis**, and look for | **Pandas Is NA** | Check DataFrame for missing values | | **Pandas Drop NA** | Drop missing values from DataFrame | +**Summary statistics** +| Node Name | Functionality | +|-------------------------|-------------------------------------| +| **Pandas Count** | Number of rows | +| **Pandas Max** | Maximum value of row values | +| **Pandas Mean** | Mean of row values | +| **Pandas Median** | Median of row values | +| **Pandas Min** | Minimum value of row values | +| **Pandas Mode** | Mode of row values | +| **Pandas Std** | Standard deviation of row values | +| **Pandas Sum** | Sum of row values | +| **Pandas Var** | Variance of row values | + **Converting to displayable string** | Node Name | Functionality | |-------------------------|-------------------------------------| @@ -192,7 +206,10 @@ https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/py/show_text.p This node has been copied to display text without requiring the user to install the above custom nodes package. However, if you already have ComfyUI-Custom-Scripts, you can also use the Show Text node from the package. ## Displaying DataFrame contents -Use **Pandas Show DataFrame** node. When the number of rows is large, Pandas automatically hides middle rows during string conversion, so the display size should be always manageable. +Use **Pandas Show DataFrame** node. When the number of rows is large, This node automatically hides middle rows during string conversion, so the display size should be always manageable. + +## Displaying Series contents +Use **Pandas Show Series** node. ## Extracting DataFrame statistics Use **Pandas Summary** to extract statistics then connect the output to **Pandas Show Text** for display. @@ -203,6 +220,31 @@ Use **Pandas Sort**. ![Pandas Sort](images/sort.jpg) +## Summary statistics +You can compute summary statistics for each row in a DataFrame using below nodes: +* **Pandas Count** to get the number of rows. +* **Pandas Min** to get the minimum row value. +* **Pandas Max** to get the maximum value of row values. +* **Pandas Mean** to get the mean of row values. +* **Pandas Median** to get the median of row values. +* **Pandas Mode** to get the mode of row values. +* **Pandas Sum** to get the sum of row values. +* **Pandas Std** to get the standard deviation of row values. +* **Pandas Var** to get the variance of row values. + +All nodes return a Series except for Pandas Mode, which returns a DataFrame. This is necessary because multiple values may occur with the highest frequency. For example: +``` +apple +apple +apple +banana +donut +coconut +coconut +coconut +``` +In this case, both apple and coconut are the mode. + ## Convert DataFrame to a string Use **Pandas To String**. This node is to convert the DataFrame to a displayable string, and this truncates the data when the data is large. This can be used to display the data content in a text display node (e.g. Show Text node). However, you can use Show DataFrame nodes to display the DataFrame content, so you do not normally need to use this. diff --git a/examples/workflows/summary_statistics.json b/examples/workflows/summary_statistics.json new file mode 100644 index 0000000..e1b40e2 --- /dev/null +++ b/examples/workflows/summary_statistics.json @@ -0,0 +1 @@ +{"last_node_id":31,"last_link_id":31,"nodes":[{"id":28,"type":"PandasShowSeries","pos":[-415.3694763183594,1932.43359375],"size":[457.93426513671875,79.50759887695312],"flags":{},"order":13,"mode":0,"inputs":[{"name":"series_json","type":"STRING","link":25,"widget":{"name":"series_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowSeries"},"widgets_values":["","{\"array\": [16.0, 101.1, 351.2], \"index\": [\"A\", \"B\", \"C\"], \"name\": null}"]},{"id":27,"type":"PandasShowSeries","pos":[-408.4590759277344,2071.62353515625],"size":[457.93426513671875,79.50759887695312],"flags":{},"order":14,"mode":0,"inputs":[{"name":"series_json","type":"STRING","link":26,"widget":{"name":"series_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowSeries"},"widgets_values":["","{\"array\": [6, 6, 6], \"index\": [\"A\", \"B\", \"C\"], \"name\": null}"]},{"id":26,"type":"PandasShowSeries","pos":[-403.81671142578125,2213.937255859375],"size":[457.93426513671875,79.50759887695312],"flags":{},"order":15,"mode":0,"inputs":[{"name":"series_json","type":"STRING","link":27,"widget":{"name":"series_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowSeries"},"widgets_values":["","{\"array\": [2.6666666666666665, 16.849999999999998, 58.53333333333333], \"index\": [\"A\", \"B\", \"C\"], \"name\": null}"]},{"id":25,"type":"PandasShowSeries","pos":[-399.9659423828125,2354.312255859375],"size":[457.93426513671875,79.50759887695312],"flags":{},"order":16,"mode":0,"inputs":[{"name":"series_json","type":"STRING","link":28,"widget":{"name":"series_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowSeries"},"widgets_values":["","{\"array\": [2.5, 17.5, 65.0], \"index\": [\"A\", \"B\", \"C\"], \"name\": null}"]},{"id":14,"type":"PandasShowSeries","pos":[-405.37396240234375,2862.830322265625],"size":[457.93426513671875,79.50759887695312],"flags":{},"order":10,"mode":0,"inputs":[{"name":"series_json","type":"STRING","link":13,"widget":{"name":"series_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowSeries"},"widgets_values":["","{\"array\": [1.632993161855452, 10.465896999302066, 31.446886438352948], \"index\": [\"A\", \"B\", \"C\"], \"name\": null}"]},{"id":23,"type":"PandasShowSeries","pos":[-405.5749816894531,2701.80517578125],"size":[457.93426513671875,79.50759887695312],"flags":{},"order":18,"mode":0,"inputs":[{"name":"series_json","type":"STRING","link":30,"widget":{"name":"series_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowSeries"},"widgets_values":["","{\"array\": [2.666666666666667, 109.535, 988.9066666666665], \"index\": [\"A\", \"B\", \"C\"], \"name\": null}"]},{"id":15,"type":"PandasMin","pos":[-720.0925903320312,1628.4970703125],"size":[210,46],"flags":{},"order":2,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":15,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":[23],"slot_index":0}],"properties":{"Node name for S&R":"PandasMin"},"widgets_values":[""]},{"id":29,"type":"PandasShowSeries","pos":[-422.8512268066406,1787.0130615234375],"size":[457.93426513671875,79.50759887695312],"flags":{},"order":12,"mode":0,"inputs":[{"name":"series_json","type":"STRING","link":24,"widget":{"name":"series_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowSeries"},"widgets_values":["","{\"array\": [5.0, 30.0, 90.0], \"index\": [\"A\", \"B\", \"C\"], \"name\": null}"]},{"id":22,"type":"PandasVar","pos":[-705.844482421875,2697.947265625],"size":[210,51.45679473876953],"flags":{},"order":9,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":22,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":[30],"slot_index":0}],"properties":{"Node name for S&R":"PandasVar"},"widgets_values":[""]},{"id":11,"type":"PandasStd","pos":[-705.8277587890625,2862.27001953125],"size":[210,46],"flags":{},"order":1,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":10,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":[13],"slot_index":0}],"properties":{"Node name for S&R":"PandasStd"},"widgets_values":[""]},{"id":21,"type":"PandasMode","pos":[-716.1126708984375,2489.3876953125],"size":[210,46],"flags":{},"order":8,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":21,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":[31],"slot_index":0}],"properties":{"Node name for S&R":"PandasMode"},"widgets_values":[""]},{"id":19,"type":"PandasMedian","pos":[-711.084716796875,2352.453857421875],"size":[210,46],"flags":{},"order":7,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":20,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":[28],"slot_index":0}],"properties":{"Node name for S&R":"PandasMedian"},"widgets_values":[""]},{"id":20,"type":"PandasMean","pos":[-708.3650512695312,2212.2548828125],"size":[210,46],"flags":{},"order":6,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":19,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":[27],"slot_index":0}],"properties":{"Node name for S&R":"PandasMean"},"widgets_values":[""]},{"id":18,"type":"PandasCount","pos":[-713.810302734375,2074.4892578125],"size":[210,46],"flags":{},"order":5,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":18,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":[26],"slot_index":0}],"properties":{"Node name for S&R":"PandasCount"},"widgets_values":[""]},{"id":17,"type":"PandasSum","pos":[-708.5211181640625,1936.2083740234375],"size":[210,46],"flags":{},"order":4,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":17,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":[25],"slot_index":0}],"properties":{"Node name for S&R":"PandasSum"},"widgets_values":[""]},{"id":16,"type":"PandasMax","pos":[-716.6470336914062,1790.6435546875],"size":[210,46],"flags":{},"order":3,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":16,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":[24],"slot_index":0}],"properties":{"Node name for S&R":"PandasMax"},"widgets_values":[""]},{"id":30,"type":"PandasShowSeries","pos":[-419.97979736328125,1625.7716064453125],"size":[457.93426513671875,79.50759887695312],"flags":{},"order":11,"mode":0,"inputs":[{"name":"series_json","type":"STRING","link":23,"widget":{"name":"series_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowSeries"},"widgets_values":["","{\"array\": [1.0, 1.1, 1.2], \"index\": [\"A\", \"B\", \"C\"], \"name\": null}"]},{"id":31,"type":"PandasShowDataFrame","pos":[-402.2547912597656,2487.5908203125],"size":[458.6945495605469,155.69699096679688],"flags":{},"order":17,"mode":0,"inputs":[{"name":"dataframe_json","type":"STRING","link":31,"widget":{"name":"dataframe_json"}}],"outputs":[{"name":"STRING","type":"STRING","links":null,"shape":6}],"properties":{"Node name for S&R":"PandasShowDataFrame"},"widgets_values":[""," A B C\n0 1.0 1.1 1.2\n1 NaN 10.0 50.0\n2 NaN 15.0 60.0\n3 NaN 20.0 70.0\n4 NaN 25.0 80.0\n5 NaN 30.0 90.0"]},{"id":10,"type":"PandasCreate","pos":[-1051.1636962890625,1629.7008056640625],"size":[210,152.64755249023438],"flags":{},"order":0,"mode":0,"inputs":[],"outputs":[{"name":"STRING","type":"STRING","links":[10,15,16,17,18,19,20,21,22],"slot_index":0}],"properties":{"Node name for S&R":"PandasCreate"},"widgets_values":["A,B,C\n1, 1.1, 1.2\n1, 10, 50\n2, 15, 60\n3, 20, 70\n4, 25, 80\n5, 30, 90"]}],"links":[[10,10,0,11,0,"STRING"],[13,11,0,14,0,"STRING"],[15,10,0,15,0,"STRING"],[16,10,0,16,0,"STRING"],[17,10,0,17,0,"STRING"],[18,10,0,18,0,"STRING"],[19,10,0,20,0,"STRING"],[20,10,0,19,0,"STRING"],[21,10,0,21,0,"STRING"],[22,10,0,22,0,"STRING"],[23,15,0,30,0,"STRING"],[24,16,0,29,0,"STRING"],[25,17,0,28,0,"STRING"],[26,18,0,27,0,"STRING"],[27,20,0,26,0,"STRING"],[28,19,0,25,0,"STRING"],[30,22,0,23,0,"STRING"],[31,21,0,31,0,"STRING"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.6830134553650705,"offset":[1528.0972449918231,-1148.155311578934]},"node_versions":{"data-analysis":"b3ed83925193071e4380e085056fb93bde54b2a0"},"ue_links":[]},"version":0.4} \ No newline at end of file diff --git a/modules/pandas_wrapper/pandas_count.py b/modules/pandas_wrapper/pandas_count.py new file mode 100644 index 0000000..ee7464b --- /dev/null +++ b/modules/pandas_wrapper/pandas_count.py @@ -0,0 +1,42 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd +from .utils import series_to_jsons + +class PandasCount: + """ + PandasCount: + A class for computing the count of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe_json: str) -> tuple: + """ + Returns a Series with count for each column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a JSON string for the Series. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json)) + return (series_to_jsons(df.count()),) diff --git a/modules/pandas_wrapper/pandas_max.py b/modules/pandas_wrapper/pandas_max.py new file mode 100644 index 0000000..b780620 --- /dev/null +++ b/modules/pandas_wrapper/pandas_max.py @@ -0,0 +1,42 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd +from .utils import series_to_jsons + +class PandasMax: + """ + PandasMin: + A class for computing the max of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe_json: str) -> tuple: + """ + Returns a Series with max for each column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a JSON string for the Series. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json)) + return (series_to_jsons(df.max()),) diff --git a/modules/pandas_wrapper/pandas_mean.py b/modules/pandas_wrapper/pandas_mean.py new file mode 100644 index 0000000..453b348 --- /dev/null +++ b/modules/pandas_wrapper/pandas_mean.py @@ -0,0 +1,42 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd +from .utils import series_to_jsons + +class PandasMean: + """ + PandasMin: + A class for computing the mean of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe_json: str) -> tuple: + """ + Returns a Series with mean for each column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a JSON string for the Series. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json)) + return (series_to_jsons(df.mean()),) diff --git a/modules/pandas_wrapper/pandas_median.py b/modules/pandas_wrapper/pandas_median.py new file mode 100644 index 0000000..1b2d2f3 --- /dev/null +++ b/modules/pandas_wrapper/pandas_median.py @@ -0,0 +1,42 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd +from .utils import series_to_jsons + +class PandasMedian: + """ + PandasMin: + A class for computing the median of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe_json: str) -> tuple: + """ + Returns a Series with median for each column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a JSON string for the Series. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json)) + return (series_to_jsons(df.median()),) diff --git a/modules/pandas_wrapper/pandas_min.py b/modules/pandas_wrapper/pandas_min.py new file mode 100644 index 0000000..cf9f308 --- /dev/null +++ b/modules/pandas_wrapper/pandas_min.py @@ -0,0 +1,42 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd +from .utils import series_to_jsons + +class PandasMin: + """ + PandasMin: + A class for computing the min of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe_json: str) -> tuple: + """ + Returns a Series with min for each column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a JSON string for the Series. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json)) + return (series_to_jsons(df.min()),) diff --git a/modules/pandas_wrapper/pandas_mode.py b/modules/pandas_wrapper/pandas_mode.py new file mode 100644 index 0000000..c12643d --- /dev/null +++ b/modules/pandas_wrapper/pandas_mode.py @@ -0,0 +1,41 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd + +class PandasMode: + """ + PandasMode: + A class for computing the mode of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe_json: str) -> tuple: + """ + Returns a DataFrame with mode for each column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a JSON string for the DataFrame. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json)) + return (df.mode().to_json(),) diff --git a/modules/pandas_wrapper/pandas_show_series.py b/modules/pandas_wrapper/pandas_show_series.py new file mode 100644 index 0000000..e86f500 --- /dev/null +++ b/modules/pandas_wrapper/pandas_show_series.py @@ -0,0 +1,56 @@ +""" +This code is based on +https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/py/show_text.py + +See credit/credit.md for the full license. +""" +from io import StringIO +import pandas as pd +from .utils import series_to_jsons +from .utils import jsons_to_series + +class PandasShowSeries: + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "series_json": ("STRING", {"forceInput": True}), + }, + "hidden": { + "unique_id": "UNIQUE_ID", + "extra_pnginfo": "EXTRA_PNGINFO", + }, + } + + INPUT_IS_LIST = True + RETURN_TYPES = ("STRING",) + FUNCTION = "notify" + OUTPUT_NODE = True + OUTPUT_IS_LIST = (True,) + + CATEGORY = "Data Analysis" + + def notify(self, series_json, unique_id=None, extra_pnginfo=None): + text = None + if unique_id is not None and extra_pnginfo is not None: + if not isinstance(extra_pnginfo, list): + print("Error: extra_pnginfo is not a list") + elif ( + not isinstance(extra_pnginfo[0], dict) + or "workflow" not in extra_pnginfo[0] + ): + print("Error: extra_pnginfo[0] is not a dict or missing 'workflow' key") + else: + # Deserialize JSON string to DataFrame + series = jsons_to_series(series_json[0]) + text = [series_to_jsons(series)] # wrap in list + workflow = extra_pnginfo[0]["workflow"] + node = next( + (x for x in workflow["nodes"] if str(x["id"]) == str(unique_id[0])), + None, + ) + if node: + node["widgets_values"] = [text] + else: + print("unique_id or extra_pnginfo is None.") + return {"ui": {"text": text}, "result": (text,)} diff --git a/modules/pandas_wrapper/pandas_std.py b/modules/pandas_wrapper/pandas_std.py new file mode 100644 index 0000000..775c624 --- /dev/null +++ b/modules/pandas_wrapper/pandas_std.py @@ -0,0 +1,42 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd +from .utils import series_to_jsons + +class PandasStd: + """ + PandasStd: + A class for computing the standard deviation of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe_json: str) -> tuple: + """ + Returns a Series with standard deviation for each column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a JSON string for the Series. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json)) + return (series_to_jsons(df.std()),) diff --git a/modules/pandas_wrapper/pandas_sum.py b/modules/pandas_wrapper/pandas_sum.py new file mode 100644 index 0000000..af5e63e --- /dev/null +++ b/modules/pandas_wrapper/pandas_sum.py @@ -0,0 +1,42 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd +from .utils import series_to_jsons + +class PandasSum: + """ + PandasSum: + A class for computing the sum of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe_json: str) -> tuple: + """ + Returns a Series with sum for each column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a JSON string for the Series. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json)) + return (series_to_jsons(df.sum()),) diff --git a/modules/pandas_wrapper/pandas_var.py b/modules/pandas_wrapper/pandas_var.py new file mode 100644 index 0000000..900fd70 --- /dev/null +++ b/modules/pandas_wrapper/pandas_var.py @@ -0,0 +1,42 @@ +from typing import Any, Dict +from io import StringIO +import pandas as pd +from .utils import series_to_jsons + +class PandasVar: + """ + PandasVar: + A class for computing the variance of a pandas DataFrame. + """ + + @classmethod + def INPUT_TYPES(cls) -> Dict[str, Any]: + """ + Defines the input types for the function. + + Returns: + Dict[str, Any]: A dictionary specifying required input types. + """ + return { + "required": { + "dataframe_json": ("STRING", {"multiline": True}) + } + } + + RETURN_TYPES: tuple = ("STRING",) + FUNCTION: str = "f" + CATEGORY: str = "Data Analysis" + + def f(self, dataframe_json: str) -> tuple: + """ + Returns a Series with variance for each column. + + Args: + dataframe_json (str): A JSON string representation of the DataFrame. + + Returns: + tuple: A tuple containing a JSON string for the Series. + """ + # Deserialize JSON string to DataFrame + df = pd.read_json(StringIO(dataframe_json)) + return (series_to_jsons(df.var()),) diff --git a/pyproject.toml b/pyproject.toml index 2d346f3..be4767b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "data-analysis" description = "Data analysis custom modules for ComfyUI - Use Pandas & Matplotlib within ComfyUI" -version = "1.0.18" +version = "1.0.19" license = {file = "LICENSE"} [project.urls] diff --git a/web/js/ShowSeries.js b/web/js/ShowSeries.js new file mode 100644 index 0000000..c721c06 --- /dev/null +++ b/web/js/ShowSeries.js @@ -0,0 +1,64 @@ +/* +This code is based on +https://github.com/pythongosssss/ComfyUI-Custom-Scripts/blob/main/web/js/showText.js + +See credit/credit.md for the full license. +*/ + +import { app } from "../../../scripts/app.js"; +import { ComfyWidgets } from "../../../scripts/widgets.js"; + +// Displays input text on a node +app.registerExtension({ + name: "HowToSD.ShowSeries", + async beforeRegisterNodeDef(nodeType, nodeData, app) { + if (nodeData.name === "PandasShowSeries") { // This needs to match NODE_CLASS_MAPPINGS in Python + function populate(text) { + if (this.widgets) { + for (let i = 1; i < this.widgets.length; i++) { + this.widgets[i].onRemove?.(); + } + this.widgets.length = 1; + } + + const v = [...text]; + if (!v[0]) { + v.shift(); + } + for (const list of v) { + const w = ComfyWidgets["STRING"](this, "text2", ["STRING", { multiline: true }], app).widget; + w.inputEl.readOnly = true; + w.inputEl.style.opacity = 0.6; + w.value = list; + } + + requestAnimationFrame(() => { + const sz = this.computeSize(); + if (sz[0] < this.size[0]) { + sz[0] = this.size[0]; + } + if (sz[1] < this.size[1]) { + sz[1] = this.size[1]; + } + this.onResize?.(sz); + app.graph.setDirtyCanvas(true, false); + }); + } + + // When the node is executed we will be sent the input text, display this in the widget + const onExecuted = nodeType.prototype.onExecuted; + nodeType.prototype.onExecuted = function (message) { + onExecuted?.apply(this, arguments); + populate.call(this, message.text); + }; + + const onConfigure = nodeType.prototype.onConfigure; + nodeType.prototype.onConfigure = function () { + onConfigure?.apply(this, arguments); + if (this.widgets_values?.length) { + populate.call(this, this.widgets_values.slice(+this.widgets_values.length > 1)); + } + }; + } + }, +}); \ No newline at end of file