diff --git a/__init__.py b/__init__.py
index 460d1af..3a596ea 100644
--- a/__init__.py
+++ b/__init__.py
@@ -34,6 +34,7 @@ from .modules.pandas_wrapper.pandas_create import PandasCreate
from .modules.pandas_wrapper.pandas_create_from_dict import PandasCreateFromDict
from .modules.pandas_wrapper.pandas_create_from_multiple_dict import PandasCreateFromMultipleDict
from .modules.pandas_wrapper.pandas_create_from_numpy import PandasCreateFromNumpy
+from .modules.pandas_wrapper.pandas_create_from_tensor import PandasCreateFromTensor
from .modules.pandas_wrapper.pandas_create_series_from_dict import PandasCreateSeriesFromDict
from .modules.pandas_wrapper.pandas_create_with_index import PandasCreateWithIndex
from .modules.pandas_wrapper.pandas_crosstab import PandasCrosstab
@@ -88,6 +89,7 @@ from .modules.pandas_wrapper.pandas_pow_scalar_float import PandasPowScalarFloat
from .modules.pandas_wrapper.pandas_pow_scalar_int import PandasPowScalarInt
from .modules.pandas_wrapper.pandas_pow_series import PandasPowSeries
from .modules.pandas_wrapper.pandas_rename import PandasRename
+from .modules.pandas_wrapper.pandas_rename_advanced import PandasRenameAdvanced
from .modules.pandas_wrapper.pandas_replace import PandasReplace
from .modules.pandas_wrapper.pandas_save_csv import PandasSaveCSV
from .modules.pandas_wrapper.pandas_save_json import PandasSaveJSON
@@ -254,6 +256,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
"PandasCreateFromDict": PandasCreateFromDict,
"PandasCreateFromMultipleDict": PandasCreateFromMultipleDict,
"PandasCreateFromNumpy": PandasCreateFromNumpy,
+ "PandasCreateFromTensor": PandasCreateFromTensor,
"PandasCreateSeriesFromDict": PandasCreateSeriesFromDict,
"PandasCreateWithIndex": PandasCreateWithIndex,
"PandasCrosstab": PandasCrosstab,
@@ -308,6 +311,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
"PandasPowScalarInt": PandasPowScalarInt,
"PandasPowSeries": PandasPowSeries,
"PandasRename": PandasRename,
+ "PandasRenameAdvanced": PandasRenameAdvanced,
"PandasReplace": PandasReplace,
"PandasSaveCSV": PandasSaveCSV,
"PandasSaveJSON": PandasSaveJSON,
@@ -477,6 +481,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
"PandasCreateFromDict": "Pandas Create From Dict",
"PandasCreateFromMultipleDict": "Pandas Create From Multiple Dict",
"PandasCreateFromNumpy": "Pandas Create From Numpy",
+ "PandasCreateFromTensor": "Pandas Create From Tensor",
"PandasCreateSeriesFromDict": "Pandas Create Series From Dict",
"PandasCreateWithIndex": "Pandas Create With Index",
"PandasCrosstab": "Pandas Crosstab",
@@ -531,6 +536,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
"PandasPowScalarInt": "Pandas Pow Scalar Int",
"PandasPowSeries": "Pandas Pow Series",
"PandasRename": "Pandas Rename",
+ "PandasRenameAdvanced": "Pandas Rename Advanced",
"PandasReplace": "Pandas Replace",
"PandasSaveCSV": "Pandas Save CSV",
"PandasSaveJSON": "Pandas Save JSON",
diff --git a/docs/reference/node_reference.md b/docs/reference/node_reference.md
index 81f60c7..285b3fe 100644
--- a/docs/reference/node_reference.md
+++ b/docs/reference/node_reference.md
@@ -103,6 +103,7 @@
| [Pandas Create From Dict](pandas_create_from_dict.md) | Creates a pandas DataFrame from a Python dictionary. |
| [Pandas Create From Multiple Dict](pandas_create_from_multiple_dict.md) | Creates a pandas DataFrame from multiple Python dictionaries. |
| [Pandas Create From Numpy](pandas_create_from_numpy.md) | Creates a pandas DataFrame from a NumPy ndarray. |
+| [Pandas Create From Tensor](pandas_create_from_tensor.md) | Creates a pandas DataFrame from a PyTorch tensor. |
| [Pandas Create Series From Dict](pandas_create_series_from_dict.md) | Creates a Pandas Series from a Python dictionary. |
| [Pandas Create With Index](pandas_create_with_index.md) | Creates a pandas DataFrame using values entered in the text field. Input data is assumed to have an index. |
| [Pandas Load CSV](pandas_load_csv.md) | Loads CSV files into a pandas DataFrame. |
@@ -297,6 +298,7 @@
| [Pandas Melt](pandas_melt.md) | Performs an unpivot operation (melt) on a pandas DataFrame. |
| [Pandas Pivot](pandas_pivot.md) | Computes a pivot table from a pandas DataFrame. |
| [Pandas Rename](pandas_rename.md) | Renames an index (row label) or a column label. |
+| [Pandas Rename Advanced](pandas_rename_advanced.md) | Renames one or more indices (row labels) or column labels to new labels. |
| [Pandas Series To Data Frame](pandas_series_to_dataframe.md) | Converts a Pandas Series to a DataFrame. |
| [Pandas Set Index](pandas_set_index.md) | Create index (row label) from existing column or columns. |
| [Pandas Sort](pandas_sort.md) | Sorts a pandas DataFrame by a specified column. |
diff --git a/docs/reference/pandas_create_from_tensor.md b/docs/reference/pandas_create_from_tensor.md
new file mode 100644
index 0000000..866928f
--- /dev/null
+++ b/docs/reference/pandas_create_from_tensor.md
@@ -0,0 +1,17 @@
+# Pandas Create From Tensor
+Creates a pandas DataFrame from a PyTorch tensor.
+
+## Input
+| Name | Data type |
+|---|---|
+| tens | Tensor |
+
+## Output
+| Data type |
+|---|
+| DataFrame |
+
+
+Category: IO
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/docs/reference/pandas_horizontal_concat.md b/docs/reference/pandas_horizontal_concat.md
index 1cb4276..064c1bb 100644
--- a/docs/reference/pandas_horizontal_concat.md
+++ b/docs/reference/pandas_horizontal_concat.md
@@ -1,11 +1,14 @@
# Pandas Horizontal Concat
Horizontally concatenates two pandas DataFrames.
+If reset labels is set to True, column labels are reset to a sequential integer index.
+
## Input
| Name | Data type |
|---|---|
| a_dataframe | DataFrame |
| b_dataframe | DataFrame |
+| reset_labels | Boolean |
## Output
| Data type |
diff --git a/docs/reference/pandas_rename_advanced.md b/docs/reference/pandas_rename_advanced.md
new file mode 100644
index 0000000..75a297e
--- /dev/null
+++ b/docs/reference/pandas_rename_advanced.md
@@ -0,0 +1,28 @@
+# Pandas Rename Advanced
+Renames one or more indices (row labels) or column labels to new labels.
+
+Specify the replacement in a Python dictionary format:
+```
+{
+ "old_foo": "new_bar",
+ 0: 5
+}
+```
+A string label has to be double-quoted.
+
+## Input
+| Name | Data type |
+|---|---|
+| dataframe | DataFrame |
+| axis | |
+| replacement_pairs | String |
+
+## Output
+| Data type |
+|---|
+| DataFrame |
+
+
+Category: Transformation
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/modules/matplotlib/mpl_bar.py b/modules/matplotlib/mpl_bar.py
index 5e2a591..6815dea 100644
--- a/modules/matplotlib/mpl_bar.py
+++ b/modules/matplotlib/mpl_bar.py
@@ -4,6 +4,7 @@ from io import StringIO
import pandas as pd
from .common import common_input_types
from .util import plot_post_steps
+from ..pandas_wrapper.utils import column_label_string_to_target_type
class MPLBar:
"""
@@ -56,6 +57,8 @@ class MPLBar:
# Create the plot
fig, ax = plt.subplots()
+ x_column_name = column_label_string_to_target_type(df, x_column_name)
+ y_column_name = column_label_string_to_target_type(df, y_column_name)
ax.bar(df[x_column_name], df[y_column_name])
return plot_post_steps(
diff --git a/modules/matplotlib/mpl_line.py b/modules/matplotlib/mpl_line.py
index 24077e8..1eebc7b 100644
--- a/modules/matplotlib/mpl_line.py
+++ b/modules/matplotlib/mpl_line.py
@@ -4,6 +4,7 @@ from io import StringIO
import pandas as pd
from .common import common_input_types
from .util import plot_post_steps
+from ..pandas_wrapper.utils import column_label_string_to_target_type
class MPLLine:
"""
@@ -53,6 +54,8 @@ class MPLLine:
"""
df = dataframe
+ x_column_name = column_label_string_to_target_type(df, x_column_name)
+ y_column_name = column_label_string_to_target_type(df, y_column_name)
# Create the plot
fig, ax = plt.subplots()
diff --git a/modules/matplotlib/mpl_pie_chart.py b/modules/matplotlib/mpl_pie_chart.py
index 6a3f965..48e66b5 100644
--- a/modules/matplotlib/mpl_pie_chart.py
+++ b/modules/matplotlib/mpl_pie_chart.py
@@ -2,6 +2,7 @@ from typing import Any, Dict
from matplotlib import pyplot as plt
import pandas as pd
from .util import plot_post_steps
+from ..pandas_wrapper.utils import column_label_string_to_target_type
class MPLPieChart:
"""
@@ -54,6 +55,8 @@ class MPLPieChart:
"""
df = dataframe
+ label_column_name = column_label_string_to_target_type(df, label_column_name)
+ data_column_name = column_label_string_to_target_type(df, data_column_name)
# Create the plot
fig, ax = plt.subplots()
diff --git a/modules/matplotlib/mpl_scatter.py b/modules/matplotlib/mpl_scatter.py
index 635b604..59b5448 100644
--- a/modules/matplotlib/mpl_scatter.py
+++ b/modules/matplotlib/mpl_scatter.py
@@ -4,6 +4,7 @@ from io import StringIO
import pandas as pd
from .common import common_input_types
from .util import plot_post_steps
+from ..pandas_wrapper.utils import column_label_string_to_target_type
class MPLScatter:
"""
@@ -53,6 +54,8 @@ class MPLScatter:
"""
df = dataframe
+ x_column_name = column_label_string_to_target_type(df, x_column_name)
+ y_column_name = column_label_string_to_target_type(df, y_column_name)
# Create the plot
fig, ax = plt.subplots()
diff --git a/modules/pandas_wrapper/pandas_create_from_tensor.py b/modules/pandas_wrapper/pandas_create_from_tensor.py
new file mode 100644
index 0000000..6818c4e
--- /dev/null
+++ b/modules/pandas_wrapper/pandas_create_from_tensor.py
@@ -0,0 +1,48 @@
+from typing import Any, Dict
+import pandas as pd
+import torch
+
+
+class PandasCreateFromTensor:
+ """
+ Creates a pandas DataFrame from a PyTorch tensor.
+
+ category: IO
+ """
+
+ @classmethod
+ def INPUT_TYPES(cls) -> Dict[str, Any]:
+ """
+ Defines the input types for the `create` function.
+
+ Returns:
+ Dict[str, Any]: A dictionary specifying required input types.
+ """
+ return {
+ "required": {
+ "tens": ("TENSOR", {}),
+ }
+ }
+
+ RETURN_TYPES: tuple = ("DATAFRAME",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self, tens: torch.Tensor) -> tuple:
+ """
+ Creates a pandas DataFrame from a PyTorch tensor.
+
+ Args:
+ tens (torch.Tensor): A PyTorch tensor.
+
+ Returns:
+ tuple: A tuple containing the DataFrame.
+
+ Raises:
+ ValueError: If the tensor is not rank 1 or rank 2.
+ """
+ if tens.dim() not in [1, 2]:
+ raise ValueError("Only rank 1 and rank 2 tensors are supported.")
+
+ return (pd.DataFrame(tens.numpy()),)
+
diff --git a/modules/pandas_wrapper/pandas_horizontal_concat.py b/modules/pandas_wrapper/pandas_horizontal_concat.py
index cca6cce..a4cebec 100644
--- a/modules/pandas_wrapper/pandas_horizontal_concat.py
+++ b/modules/pandas_wrapper/pandas_horizontal_concat.py
@@ -5,6 +5,8 @@ import pandas as pd
class PandasHorizontalConcat:
"""
Horizontally concatenates two pandas DataFrames.
+
+ If reset labels is set to True, column labels are reset to a sequential integer index.
category: Transformation
"""
@@ -20,7 +22,8 @@ class PandasHorizontalConcat:
return {
"required": {
"a_dataframe": ("DATAFRAME", {}),
- "b_dataframe": ("DATAFRAME", {})
+ "b_dataframe": ("DATAFRAME", {}),
+ "reset_labels": ("BOOLEAN", {"default": False})
}
}
@@ -28,16 +31,23 @@ class PandasHorizontalConcat:
FUNCTION: str = "f"
CATEGORY: str = "Data Analysis"
- def f(self, a_dataframe: pd.DataFrame, b_dataframe: pd.DataFrame) -> tuple:
+ def f(self,
+ a_dataframe: pd.DataFrame,
+ b_dataframe: pd.DataFrame,
+ reset_labels: bool) -> tuple:
"""
Horizontally concatenate two pandas DataFrames.
Args:
a_dataframe (DataFrame): The first DataFrame.
b_dataframe (DataFrame): The second DataFrame.
+ reset_labels (bool): If reset labels is set to True, column labels are reset to a sequential integer index.
Returns:
tuple: A tuple containing the concatenated DataFrame.
"""
df_out = pd.concat([a_dataframe, b_dataframe], axis=1)
+ if reset_labels:
+ df_out.columns = range(df_out.shape[1])
+
return (df_out,)
diff --git a/modules/pandas_wrapper/pandas_rename_advanced.py b/modules/pandas_wrapper/pandas_rename_advanced.py
new file mode 100644
index 0000000..af0fee1
--- /dev/null
+++ b/modules/pandas_wrapper/pandas_rename_advanced.py
@@ -0,0 +1,72 @@
+from typing import Any, Dict
+import pandas as pd
+import ast
+
+class PandasRenameAdvanced:
+ """
+ Renames one or more indices (row labels) or column labels to new labels.
+
+ Specify the replacement in a Python dictionary format:
+ ```
+ {
+ "old_foo": "new_bar",
+ 0: 5
+ }
+ ```
+ A string label has to be double-quoted.
+
+ category: Transformation
+ """
+
+ @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": ("DATAFRAME", {}),
+ "axis": (("columns", "index"),),
+ "replacement_pairs": ("STRING", {"default":"{}", "multiline": True})
+ }
+ }
+
+ RETURN_TYPES: tuple = ("DATAFRAME",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self, dataframe: pd.DataFrame, axis: str, replacement_pairs: str) -> tuple:
+ """
+ Renames one or more indices (row labels) or column labels to new labels.
+
+ Args:
+ dataframe (DataFrame): The DataFrame.
+ axis (str): Index (row label) or column label.
+ replacement_pairs (str): Replacement pairs specified in the Python dictionary format.
+
+ Returns:
+ tuple: A tuple containing the DataFrame.
+ """
+ try:
+ d = ast.literal_eval(replacement_pairs)
+ if not isinstance(d, dict):
+ raise ValueError("replacement_pairs must be a dictionary.")
+
+ # Check that values in the dictionary are not lists, tuples, or dictionaries
+ if any(isinstance(value, (list, tuple, dict)) for value in d.values()):
+ raise ValueError("replacement_pairs values must not be lists, tuples, or dictionaries.")
+
+ except (SyntaxError, ValueError) as e:
+ raise ValueError(f"Invalid replacement_pairs format: {e}")
+
+ if axis == "index":
+ df_out = dataframe.rename(index=d)
+ elif axis == "columns":
+ df_out = dataframe.rename(columns=d)
+ else:
+ raise ValueError("Invalid axis. Only 'index' or 'columns' are supported.")
+
+ return (df_out,)
diff --git a/modules/pandas_wrapper/utils.py b/modules/pandas_wrapper/utils.py
index 1113be3..b1f27a8 100644
--- a/modules/pandas_wrapper/utils.py
+++ b/modules/pandas_wrapper/utils.py
@@ -1,4 +1,4 @@
-from typing import Any, List, Tuple, Union, Set
+from typing import Any, List, Tuple, Union, Set, Optional
import json
import numpy as np
import pandas as pd
@@ -54,7 +54,7 @@ def column_labels_string_to_list(dataframe:pd.DataFrame, s:str, no_check:bool=Fa
Args:
dataframe (pd.DataFrame): The DataFrame to validate column names against.
s (str): A comma-separated string representing column labels.
- no_check (str): If True, input string is processed as a string without checking the column labels
+ no_check (bool): If True, input string is processed as a string without checking the column labels
of the DataFrame.
Returns:
@@ -84,6 +84,48 @@ def column_labels_string_to_list(dataframe:pd.DataFrame, s:str, no_check:bool=Fa
return output_columns
+def column_label_string_to_target_type(dataframe: pd.DataFrame, label: str, return_none: bool=False) -> Optional[Union[str, int, float]]:
+ """
+ Converts a string column label into the data type used for the corresponding label in the DataFrame.
+
+ If a column name does not exist in the DataFrame, a ValueError is raised.
+
+ Args:
+ dataframe (pd.DataFrame): The DataFrame to validate column names against.
+ label (str): A string representing a column label.
+ return_none (bool): If True, if the label is not found, None is returned instead of raising an exception.
+ Returns:
+ Union[str, int, float]: The column label in the correct type used in the DataFrame.
+
+ Raises:
+ ValueError: If `s` does not match any existing column name.
+ """
+ valid_columns = flatten_list_tuple_unique(dataframe.columns)
+ s = label.strip()
+
+ if s in valid_columns:
+ return s
+
+ try:
+ numeric_c = int(s)
+ if numeric_c in valid_columns:
+ return numeric_c
+ except ValueError:
+ pass
+
+ try:
+ numeric_c = float(s)
+ if numeric_c in valid_columns:
+ return numeric_c
+ except ValueError:
+ pass
+
+ if return_none:
+ return None
+
+ raise ValueError(f"Column '{s}' not found in the DataFrame.")
+
+
def series_to_jsons(series: pd.Series) -> str:
"""
Converts a pandas Series to a JSON string.
diff --git a/modules/seaborn_wrapper/sns_bar.py b/modules/seaborn_wrapper/sns_bar.py
index 7997192..70f1664 100644
--- a/modules/seaborn_wrapper/sns_bar.py
+++ b/modules/seaborn_wrapper/sns_bar.py
@@ -6,7 +6,7 @@ from .common import common_input_types
from .util import plot_post_steps
from .util import set_up_sns_style_palette
from .util import process_y_columns_and_labels
-
+from ..pandas_wrapper.utils import column_label_string_to_target_type
class SNSBar:
"""
@@ -63,6 +63,7 @@ class SNSBar:
"""
df = dataframe
set_up_sns_style_palette(style, palette)
+ x_column_name = column_label_string_to_target_type(df, x_column_name)
y_columns, labels = process_y_columns_and_labels(df, y_column_name, legend_label)
# Create the plot
diff --git a/modules/seaborn_wrapper/sns_boxplot.py b/modules/seaborn_wrapper/sns_boxplot.py
index 0dac6df..e4571d0 100644
--- a/modules/seaborn_wrapper/sns_boxplot.py
+++ b/modules/seaborn_wrapper/sns_boxplot.py
@@ -4,7 +4,7 @@ import pandas as pd
import seaborn as sns
from .util import plot_post_steps
from .util import set_up_sns_style_palette
-
+from ..pandas_wrapper.utils import column_label_string_to_target_type
class SNSBoxplot:
"""
@@ -81,6 +81,8 @@ class SNSBoxplot:
"""
df = dataframe
set_up_sns_style_palette(style, palette)
+ category_column_name = column_label_string_to_target_type(df, category_column_name, return_none=True)
+ value_column_name = column_label_string_to_target_type(df, value_column_name)
# Create the plot
fig, ax = plt.subplots()
diff --git a/modules/seaborn_wrapper/sns_line.py b/modules/seaborn_wrapper/sns_line.py
index d4c85fa..ef27049 100644
--- a/modules/seaborn_wrapper/sns_line.py
+++ b/modules/seaborn_wrapper/sns_line.py
@@ -6,7 +6,7 @@ from .common import common_input_types
from .util import plot_post_steps
from .util import set_up_sns_style_palette
from .util import process_y_columns_and_labels
-
+from ..pandas_wrapper.utils import column_label_string_to_target_type
class SNSLine:
"""
@@ -83,6 +83,7 @@ class SNSLine:
"""
df = dataframe
set_up_sns_style_palette(style, palette)
+ x_column_name = column_label_string_to_target_type(df, x_column_name)
y_columns, labels = process_y_columns_and_labels(df, y_column_name, legend_label)
fig, ax = plt.subplots()
diff --git a/modules/seaborn_wrapper/sns_pairplot.py b/modules/seaborn_wrapper/sns_pairplot.py
index f22ae13..11ecbab 100644
--- a/modules/seaborn_wrapper/sns_pairplot.py
+++ b/modules/seaborn_wrapper/sns_pairplot.py
@@ -4,6 +4,7 @@ import pandas as pd
import seaborn as sns
from .util import plot_post_steps
from .util import set_up_sns_style_palette
+from ..pandas_wrapper.utils import column_label_string_to_target_type
class SNSPairplot:
@@ -60,6 +61,7 @@ class SNSPairplot:
set_up_sns_style_palette(style, palette)
# Create the plot
+ hue_column_name = column_label_string_to_target_type(df, hue_column_name, return_none=True)
if hue_column_name:
pp = sns.pairplot(df, hue=hue_column_name)
else:
diff --git a/modules/seaborn_wrapper/sns_scatter.py b/modules/seaborn_wrapper/sns_scatter.py
index b069ec3..8e398c6 100644
--- a/modules/seaborn_wrapper/sns_scatter.py
+++ b/modules/seaborn_wrapper/sns_scatter.py
@@ -6,7 +6,7 @@ from .common import common_input_types
from .util import plot_post_steps
from .util import set_up_sns_style_palette
from .util import process_y_columns_and_labels
-
+from ..pandas_wrapper.utils import column_label_string_to_target_type
class SNSScatter:
"""
@@ -63,6 +63,7 @@ class SNSScatter:
"""
df = dataframe
set_up_sns_style_palette(style, palette)
+ x_column_name = column_label_string_to_target_type(df, x_column_name)
y_columns, labels = process_y_columns_and_labels(df, y_column_name, legend_label)
# Create the plot
diff --git a/pyproject.toml b/pyproject.toml
index f100b04..01503b6 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.4.5"
+version = "1.4.6"
license = {file = "LICENSE"}
[project.urls]
diff --git a/tests/pandas_wrapper/pandas_create_from_tensor_test.py b/tests/pandas_wrapper/pandas_create_from_tensor_test.py
new file mode 100644
index 0000000..64fdac9
--- /dev/null
+++ b/tests/pandas_wrapper/pandas_create_from_tensor_test.py
@@ -0,0 +1,54 @@
+import os
+import sys
+import unittest
+import torch
+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_create_from_tensor import PandasCreateFromTensor
+
+class TestPandasCreateFromTensor(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PandasCreateFromTensor()
+
+ self.test_cases = [
+ # Rank 0 (Invalid)
+ (torch.tensor(42), ValueError),
+ (torch.tensor(3.14), ValueError),
+ (torch.tensor(True), ValueError),
+
+ # Rank 1 (Valid)
+ (torch.tensor([1, 2, 3], dtype=torch.float32), pd.DataFrame([1, 2, 3], dtype="float32")),
+ (torch.tensor([1.1, 2.2, 3.3], dtype=torch.float32), pd.DataFrame([1.1, 2.2, 3.3], dtype="float32")),
+ (torch.tensor([True, False, True], dtype=torch.float32), pd.DataFrame([1.0, 0.0, 1.0], dtype="float32")),
+
+ # Rank 2 (Valid)
+ (torch.tensor([[1, 2], [3, 4]], dtype=torch.float32), pd.DataFrame([[1, 2], [3, 4]], dtype="float32")),
+ (torch.tensor([[1.1, 2.2], [3.3, 4.4]], dtype=torch.float32), pd.DataFrame([[1.1, 2.2], [3.3, 4.4]], dtype="float32")),
+
+ # Rank 3+ (Invalid)
+ (torch.rand(2, 2, 2), ValueError),
+ (torch.rand(2, 2, 2, 2), ValueError),
+ ]
+
+ def test_create_dataframe(self):
+ """Test DataFrame creation from various PyTorch tensors."""
+ for data, expected in self.test_cases:
+ with self.subTest(data=data):
+ if isinstance(expected, type) and issubclass(expected, Exception):
+ with self.assertRaises(expected):
+ self.node.f(data)
+ else:
+ retval = self.node.f(data)[0]
+
+ # Test if DataFrame structure matches
+ self.assertTrue(isinstance(retval, pd.DataFrame))
+
+ # Test if data matches
+ pd.testing.assert_frame_equal(retval, expected)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pandas_wrapper/pandas_horizontal_concat_test.py b/tests/pandas_wrapper/pandas_horizontal_concat_test.py
new file mode 100644
index 0000000..a235871
--- /dev/null
+++ b/tests/pandas_wrapper/pandas_horizontal_concat_test.py
@@ -0,0 +1,48 @@
+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_horizontal_concat import PandasHorizontalConcat
+
+
+class TestPandasHorizontalConcat(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PandasHorizontalConcat()
+
+ # Sample DataFrames
+ self.df1 = pd.DataFrame({"A": [1, 2], "B": [3, 4]})
+ self.df2 = pd.DataFrame({"C": [5, 6], "D": [7, 8]})
+
+ def test_concat_without_reset_labels(self):
+ """Test horizontal concatenation without resetting column labels."""
+ expected_df = pd.DataFrame({
+ "A": [1, 2],
+ "B": [3, 4],
+ "C": [5, 6],
+ "D": [7, 8]
+ })
+
+ retval = self.node.f(self.df1, self.df2, reset_labels=False)[0]
+ pd.testing.assert_frame_equal(retval, expected_df)
+
+ def test_concat_with_reset_labels(self):
+ """Test horizontal concatenation with resetting column labels."""
+ expected_df = pd.DataFrame({
+ 0: [1, 2],
+ 1: [3, 4],
+ 2: [5, 6],
+ 3: [7, 8]
+ })
+
+ retval = self.node.f(self.df1, self.df2, reset_labels=True)[0]
+ pd.testing.assert_frame_equal(retval, expected_df)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pandas_wrapper/pandas_rename_advanced_test.py b/tests/pandas_wrapper/pandas_rename_advanced_test.py
new file mode 100644
index 0000000..773a80c
--- /dev/null
+++ b/tests/pandas_wrapper/pandas_rename_advanced_test.py
@@ -0,0 +1,107 @@
+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_rename_advanced import PandasRenameAdvanced
+
+class TestPandasRenameAdvanced(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.node = PandasRenameAdvanced()
+
+ # Sample DataFrame
+ self.df = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [4, 5, 6],
+ 100: [7, 8, 9],
+ },
+ index=["row1", "row2", 10]
+ )
+
+ def test_rename_columns_string(self):
+ """Test renaming string column labels."""
+ replacement_pairs = '{"A": "X", "B": "Y"}'
+ expected_df = pd.DataFrame(
+ {
+ "X": [1, 2, 3],
+ "Y": [4, 5, 6],
+ 100: [7, 8, 9],
+ },
+ index=["row1", "row2", 10]
+ )
+ result_df = self.node.f(self.df, "columns", replacement_pairs)[0]
+ pd.testing.assert_frame_equal(result_df, expected_df)
+
+ def test_rename_columns_integer(self):
+ """Test renaming integer column labels."""
+ replacement_pairs = '{100: 200}'
+ expected_df = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [4, 5, 6],
+ 200: [7, 8, 9],
+ },
+ index=["row1", "row2", 10]
+ )
+ result_df = self.node.f(self.df, "columns", replacement_pairs)[0]
+ pd.testing.assert_frame_equal(result_df, expected_df)
+
+ def test_rename_index_string(self):
+ """Test renaming string row labels."""
+ replacement_pairs = '{"row1": "new_row1", "row2": "new_row2"}'
+ expected_df = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [4, 5, 6],
+ 100: [7, 8, 9],
+ },
+ index=["new_row1", "new_row2", 10]
+ )
+ result_df = self.node.f(self.df, "index", replacement_pairs)[0]
+ pd.testing.assert_frame_equal(result_df, expected_df)
+
+ def test_rename_index_integer(self):
+ """Test renaming integer row labels."""
+ replacement_pairs = '{10: 20}'
+ expected_df = pd.DataFrame(
+ {
+ "A": [1, 2, 3],
+ "B": [4, 5, 6],
+ 100: [7, 8, 9],
+ },
+ index=["row1", "row2", 20]
+ )
+ result_df = self.node.f(self.df, "index", replacement_pairs)[0]
+ pd.testing.assert_frame_equal(result_df, expected_df)
+
+ def test_invalid_replacement_pairs_list(self):
+ """Test invalid replacement_pairs format."""
+ invalid_pairs = '{"A": ["X", "Y"]}' # Not a valid mapping. List is not allowed.
+ with self.assertRaises(ValueError):
+ self.node.f(self.df, "columns", invalid_pairs)
+
+ def test_invalid_replacement_pairs_dict(self):
+ """Test invalid replacement_pairs format."""
+ invalid_pairs = '{"A": {"X": "Y"}}'
+ with self.assertRaises(ValueError):
+ self.node.f(self.df, "columns", invalid_pairs)
+
+ def test_invalid_replacement_pairs_tuple(self):
+ """Test invalid replacement_pairs format."""
+ invalid_pairs = '{"A": ()"X", "Y")}'
+ with self.assertRaises(ValueError):
+ self.node.f(self.df, "columns", invalid_pairs)
+
+ def test_invalid_axis(self):
+ """Test invalid axis value."""
+ replacement_pairs = '{"A": "X"}'
+ with self.assertRaises(ValueError):
+ self.node.f(self.df, "invalid_axis", replacement_pairs)
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pandas_wrapper/utils_column_label_string_to_target_type_test.py b/tests/pandas_wrapper/utils_column_label_string_to_target_type_test.py
new file mode 100644
index 0000000..0754d06
--- /dev/null
+++ b/tests/pandas_wrapper/utils_column_label_string_to_target_type_test.py
@@ -0,0 +1,61 @@
+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.utils import column_label_string_to_target_type
+
+class TestColumnLabelStringToTargetType(unittest.TestCase):
+ def setUp(self):
+ """Set up test data."""
+ self.df = pd.DataFrame({
+ "A": [1, 2, 3],
+ "B": [4, 5, 6],
+ "C": [7, 8, 9],
+ 10: [10, 11, 12], # Integer column name
+ 20: [13, 14, 15], # Another integer column name
+ 3.5: [16, 17, 18] # Float column name
+ })
+
+ def test_valid_string_column(self):
+ """Test valid string column names remain unchanged."""
+ self.assertEqual(column_label_string_to_target_type(self.df, "A"), "A")
+ self.assertEqual(column_label_string_to_target_type(self.df, "B"), "B")
+
+ def test_integer_column_labels(self):
+ """Test conversion when integer column names are referenced as strings."""
+ self.assertEqual(column_label_string_to_target_type(self.df, "10"), 10)
+ self.assertEqual(column_label_string_to_target_type(self.df, "20"), 20)
+
+ def test_float_column_label(self):
+ """Test conversion when float column names are referenced as strings."""
+ self.assertEqual(column_label_string_to_target_type(self.df, "3.5"), 3.5)
+
+ def test_whitespace_handling(self):
+ """Test that spaces around column names are stripped before conversion."""
+ self.assertEqual(column_label_string_to_target_type(self.df, " A "), "A")
+ self.assertEqual(column_label_string_to_target_type(self.df, " 10 "), 10)
+ self.assertEqual(column_label_string_to_target_type(self.df, " 3.5 "), 3.5)
+
+ def test_non_existent_column(self):
+ """Test behavior when a non-existent column is provided."""
+ with self.assertRaises(ValueError) as context:
+ column_label_string_to_target_type(self.df, "D")
+ self.assertIn("Column 'D' not found in the DataFrame.", str(context.exception))
+
+ def test_non_existent_column_with_return_none(self):
+ """Test behavior when a non-existent column is provided."""
+ retval = column_label_string_to_target_type(self.df, "D", return_none=True)
+ self.assertTrue(retval is None)
+
+ def test_invalid_integer_conversion(self):
+ """Test that a string that can't be converted to an integer or float raises ValueError."""
+ with self.assertRaises(ValueError) as context:
+ column_label_string_to_target_type(self.df, "xyz")
+ self.assertIn("Column 'xyz' not found in the DataFrame.", str(context.exception))
+
+if __name__ == "__main__":
+ unittest.main()