diff --git a/__init__.py b/__init__.py
index 97f1ba4..2c2583b 100644
--- a/__init__.py
+++ b/__init__.py
@@ -95,6 +95,7 @@ from .modules.pandas_wrapper.pandas_transpose import PandasTranspose
from .modules.pandas_wrapper.pandas_var import PandasVar
from .modules.pandas_wrapper.pandas_vertical_concat import PandasVerticalConcat
from .modules.pandas_wrapper.pandas_vertical_split import PandasVerticalSplit
+from .modules.pandas_wrapper.pandas_xs import PandasXs
T = TypeVar("T")
@@ -199,6 +200,7 @@ NODE_CLASS_MAPPINGS: Dict[str, Type[T]] = {
"PandasVar": PandasVar,
"PandasVerticalConcat": PandasVerticalConcat,
"PandasVerticalSplit": PandasVerticalSplit,
+ "PandasXs": PandasXs,
}
@@ -303,6 +305,7 @@ NODE_DISPLAY_NAME_MAPPINGS: Dict[str, str] = {
"PandasVar": "Pandas Var",
"PandasVerticalConcat": "Pandas Vertical Concat",
"PandasVerticalSplit": "Pandas Vertical Split",
+ "PandasXs": "Pandas Xs",
}
diff --git a/docs/reference/node_reference.md b/docs/reference/node_reference.md
index ba3e527..e60bcea 100644
--- a/docs/reference/node_reference.md
+++ b/docs/reference/node_reference.md
@@ -53,6 +53,7 @@
| [Pandas Loc Row Series](pandas_loc_row_series.md) | Selects a row from a pandas DataFrame and output as a Series. |
| [Pandas Select Columns](pandas_select_columns.md) | Selects specific columns from a pandas DataFrame. |
| [Pandas Select Rows](pandas_select_rows.md) | Selects specific rows from a pandas DataFrame based on a condition. |
+| [Pandas Xs](pandas_xs.md) | Selects a subset of a Pandas DataFrame using specified index labels or positions. |
## Data type conversion
| Node | Description |
| --- | --- |
diff --git a/docs/reference/pandas_xs.md b/docs/reference/pandas_xs.md
new file mode 100644
index 0000000..4b3512f
--- /dev/null
+++ b/docs/reference/pandas_xs.md
@@ -0,0 +1,44 @@
+# Pandas Xs
+Selects a subset of a Pandas DataFrame using specified index labels or positions.
+
+This function extracts a portion of a DataFrame based on MultiIndex labels (row indices or column labels).
+
+Users must specify:
+* The labels to select (comma-separated for multiple labels).
+* The level(s) within the MultiIndex (integer positions).
+* The axis (whether selecting from rows or columns).
+
+**Understanding "Level":**
+In a MultiIndex DataFrame, a level refers to the hierarchical position of an index within the MultiIndex, which can be referenced using a 0-based integer position or an assigned level name.
+Selecting the level using an integer position is similar to integer-based selection in `iloc`.
+
+When specifying a label, Pandas needs to know which level of the MultiIndex to search.
+For example, if selecting "dog" as a row label and the DataFrame has multiple index levels,
+you must indicate whether to look in the first index (level 0) or the second index (level 1).
+
+If selecting multiple labels, their corresponding levels should be provided as a comma-separated list.
+
+Example:
+ Given a DataFrame with a 3-level MultiIndex, the first index is at level 0, the second at level 1, and the third at level 2. Selecting "dog" and "brown" at levels 1 and 2 requires specifying 1, 2 in the Level field of the node. These values are internally converted into a list and passed to Pandas.
+
+The `axis` field specifies whether selection applies to row indices (`axis=0`) or column labels (`axis=1`).
+
+## Input
+| Name | Data type |
+|---|---|
+| dataframe | DataFrame |
+| row_or_column_label | String |
+| label_type | |
+| level | String |
+| level_type | |
+| axis | |
+
+## Output
+| Data type |
+|---|
+| DataFrame |
+
+
+Category: Data subset selection
+
+ComfyUI Data Analysis Node Reference. © 2025 Hide Inada (HowToSD.com). All rights reserved.
diff --git a/modules/pandas_wrapper/pandas_loc_row_multiindex_dataframe.py b/modules/pandas_wrapper/pandas_loc_row_multiindex_dataframe.py
index 4fdd5a5..e34c177 100644
--- a/modules/pandas_wrapper/pandas_loc_row_multiindex_dataframe.py
+++ b/modules/pandas_wrapper/pandas_loc_row_multiindex_dataframe.py
@@ -60,12 +60,14 @@ class PandasLocRowMultiIndexDataFrame:
ind_list = [int(ind) for ind in ind_list]
ind_tuple = tuple(ind_list) # Convert the list to a tuple
- if len(ind_tuple) == 1: # Do not enclose in tuple if only 1 index is specified
- ind = ind_tuple[0]
- else:
- ind = ind_tuple
+ # Ensure partial indexing is supported by adding slice(None) for missing levels
+ if len(ind_tuple) < dataframe.index.nlevels:
+ filler_count = dataframe.index.nlevels - len(ind_tuple)
+ for _ in range(filler_count):
+ ind_tuple += (slice(None),)
- # We need to wrap it to get the DataFrame. Otherwise, we will get a Series.
- ind_list = [ind]
- value = dataframe.loc[ind_list]
- return (value,)
+ result = dataframe.loc[ind_tuple]
+ if isinstance(result, pd.Series): # loc can return Series so convert to DataFrame
+ result = result.to_frame().T
+
+ return (result,)
diff --git a/modules/pandas_wrapper/pandas_xs.py b/modules/pandas_wrapper/pandas_xs.py
new file mode 100644
index 0000000..bc4655d
--- /dev/null
+++ b/modules/pandas_wrapper/pandas_xs.py
@@ -0,0 +1,112 @@
+from typing import Any, Dict
+import pandas as pd
+
+class PandasXs:
+ """
+ Selects a subset of a Pandas DataFrame using specified index labels or positions.
+
+ This function extracts a portion of a DataFrame based on MultiIndex labels (row indices or column labels).
+
+ Users must specify:
+ * The labels to select (comma-separated for multiple labels).
+ * The level(s) within the MultiIndex (integer positions).
+ * The axis (whether selecting from rows or columns).
+
+ **Understanding "Level":**
+ In a MultiIndex DataFrame, a level refers to the hierarchical position of an index within the MultiIndex, which can be referenced using a 0-based integer position or an assigned level name.
+ Selecting the level using an integer position is similar to integer-based selection in `iloc`.
+
+ When specifying a label, Pandas needs to know which level of the MultiIndex to search.
+ For example, if selecting "dog" as a row label and the DataFrame has multiple index levels,
+ you must indicate whether to look in the first index (level 0) or the second index (level 1).
+
+ If selecting multiple labels, their corresponding levels should be provided as a comma-separated list.
+
+ Example:
+ Given a DataFrame with a 3-level MultiIndex, the first index is at level 0, the second at level 1, and the third at level 2. Selecting "dog" and "brown" at levels 1 and 2 requires specifying 1, 2 in the Level field of the node. These values are internally converted into a list and passed to Pandas.
+
+ The `axis` field specifies whether selection applies to row indices (`axis=0`) or column labels (`axis=1`).
+
+ category: Data subset selection
+ """
+
+ @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", {}),
+ "row_or_column_label": ("STRING", {"default": ""}),
+ "label_type":(("string", "int"),),
+ "level": ("STRING", {"default": ""}),
+ "level_type":(("int", "string"),),
+ "axis": (("index", "columns"),)
+ }
+ }
+
+ RETURN_TYPES: tuple = ("DATAFRAME",)
+ FUNCTION: str = "f"
+ CATEGORY: str = "Data Analysis"
+
+ def f(self, dataframe: pd.DataFrame,
+ row_or_column_label: str,
+ label_type: str,
+ level: str,
+ level_type: str,
+ axis: str) -> tuple:
+ """
+ Selects a subset of a Pandas DataFrame using specified index labels or positions.
+
+ Args:
+ dataframe (DataFrame): The DataFrame.
+ row_or_column (str): A comma-separated MultiIndex label for the row or label.
+ label_type (str): The data type of the row index or column label.
+ level (str): A comma-separated level list for the MultiIndex to select.
+ level_type (str): The data type of the level.
+ axis (str): Specifies whether labels are for a row or column.
+
+ Returns:
+ tuple: A tuple containing the Pandas DataFrame.
+ """
+ # Extract index list
+ # We need to pass a tuple of MultiIndex.
+ # e.g. if the first index is A, second is B, we need to pass:
+ # ("A", "B")
+ ind_list = [e.strip() for e in row_or_column_label.split(",")]
+
+ # This will throw an exception if the MultiIndex is not an int.
+ if label_type == "int":
+ ind_list = [int(e) for e in ind_list]
+
+ ind_tuple = tuple(ind_list) # Convert the list to a tuple
+ if len(ind_tuple) == 1: # Do not enclose in tuple if only 1 index is specified
+ ind = ind_tuple[0]
+ else:
+ ind = ind_tuple
+
+ # Extract level list
+ if len(level) == 0:
+ level = None
+ else:
+ level_list = [e.strip() for e in level.split(",")]
+
+ # This will throw an exception if the MultiIndex is not an int.
+ if level_type == "int":
+ level_list = [int(e) for e in level_list]
+
+ if len(level_list) == 1:
+ level = level_list[0]
+ else:
+ level = level_list
+
+ df_out = dataframe.sort_index().xs(
+ ind,
+ level=level,
+ axis=axis
+ )
+ return (df_out,)
diff --git a/pyproject.toml b/pyproject.toml
index 5df23da..4cd4f41 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.1.17"
+version = "1.1.18"
license = {file = "LICENSE"}
[project.urls]