Repository: Project-MONAI/MONAI
File: monai/data/utils.py (Python)

Code before:
                    "ehr_1": {"type": float, "default": 0.0},
                }

        col_groups: args to group the loaded columns to generate a new column,
            it should be a dictionary, every item maps to a group, the `key` will
            be the new column name, the `value` is the names of columns to combine. for example:
            `col_groups={"ehr": [f"ehr_{i}" for i in range(10)], "meta": ["meta_1", "meta_2"]}`
        kwargs: additional arguments for `pandas.merge()` API to join tables.

    """
    df = reduce(lambda l, r: pd.merge(l, r, **kwargs), ensure_tuple(dfs))
    # parse row indices
    rows: list[int | str] = []
    if row_indices is None:
        rows = slice(df.shape[0])  # type: ignore
    else:
        for i in row_indices:
            if isinstance(i, (tuple, list)):
                if len(i) != 2:
                    raise ValueError("range of row indices must contain 2 values: start and end.")
                rows.extend(list(range(i[0], i[1])))
            else:
                rows.append(i)

    # convert to a list of dictionaries corresponding to every row
    data_ = df.iloc[rows] if col_names is None else df.iloc[rows][col_names]
    if isinstance(col_types, dict):
        # fill default values for NaN
        defaults = {k: v["default"] for k, v in col_types.items() if v is not None and v.get("default") is not None}
        if defaults:
            data_ = data_.fillna(value=defaults)
        # convert data types
        types = {k: v["type"] for k, v in col_types.items() if v is not None and "type" in v}
        if types:
            data_ = data_.astype(dtype=types, copy=False)
    data: list[dict] = data_.to_dict(orient="records")

    # group columns to generate new column
    if col_groups is not None:
        groups: dict[str, list] = {}
        for name, cols in col_groups.items():
            groups[name] = df.iloc[rows][cols].values
        # invert items of groups to every row of data
        data = [dict(d, **{k: v[i] for k, v in groups.items()}) for i, d in enumerate(data)]

    return data


def orientation_ras_lps(affine: NdarrayTensor) -> NdarrayTensor:
    """
    Convert the ``affine`` between the `RAS` and `LPS` orientation

Review comment (suggestion):
Hi @bartosz-grabowski, there might be a problem with updating in this manner. The `.loc` method relies on a label index, whereas `.iloc` uses a positional index. So it may not works when rows are generated with specific `row_indices`.

```
import numpy
import pandas
import monai

df = pandas.DataFrame(numpy.random.random((50, 3)))
df_subset = df.iloc[numpy.arange(0, 50, 2)]

row_indices = [2, 10, 40]
df_subset.iloc[row_indices]
# Throw out-of-bounds index error.
```

What do you think? also cc @ericspod 

Code after:
                    "ehr_1": {"type": float, "default": 0.0},
                }

        col_groups: args to group the loaded columns to generate a new column,
            it should be a dictionary, every item maps to a group, the `key` will
            be the new column name, the `value` is the names of columns to combine. for example:
            `col_groups={"ehr": [f"ehr_{i}" for i in range(10)], "meta": ["meta_1", "meta_2"]}`
        kwargs: additional arguments for `pandas.merge()` API to join tables.

    """
    df = reduce(lambda l, r: pd.merge(l, r, **kwargs), ensure_tuple(dfs))
    # parse row indices
    rows: list[int | str] = []
    if row_indices is None:
        rows = df.index.tolist()
    else:
        for i in row_indices:
            if isinstance(i, (tuple, list)):
                if len(i) != 2:
                    raise ValueError("range of row indices must contain 2 values: start and end.")
                rows.extend(list(range(i[0], i[1])))
            else:
                rows.append(i)

    # convert to a list of dictionaries corresponding to every row
    data_ = df.loc[rows] if col_names is None else df.loc[rows, col_names]
    if isinstance(col_types, dict):
        # fill default values for NaN
        defaults = {k: v["default"] for k, v in col_types.items() if v is not None and v.get("default") is not None}
        if defaults:
            data_ = data_.fillna(value=defaults)
        # convert data types
        types = {k: v["type"] for k, v in col_types.items() if v is not None and "type" in v}
        if types:
            data_ = data_.astype(dtype=types, copy=False)
    data: list[dict] = data_.to_dict(orient="records")

    # group columns to generate new column
    if col_groups is not None:
        groups: dict[str, list] = {}
        for name, cols in col_groups.items():
            groups[name] = df.loc[rows, cols].values
        # invert items of groups to every row of data
        data = [dict(d, **{k: v[i] for k, v in groups.items()}) for i, d in enumerate(data)]

    return data


def orientation_ras_lps(affine: NdarrayTensor) -> NdarrayTensor:
    """
    Convert the ``affine`` between the `RAS` and `LPS` orientation