-
Notifications
You must be signed in to change notification settings - Fork 5
UPDATED: Add ListSource and DataFrameSource #31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,7 @@ | |||||||||||||||||
| from os import PathLike | ||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||
| from typing import Any, Literal | ||||||||||||||||||
|
|
||||||||||||||||||
| import pandas as pd | ||||||||||||||||||
| import polars as pl | ||||||||||||||||||
|
|
||||||||||||||||||
| from orcapod.core.base import Source | ||||||||||||||||||
|
|
@@ -202,3 +202,317 @@ def keys( | |||||||||||||||||
|
|
||||||||||||||||||
| def computed_label(self) -> str | None: | ||||||||||||||||||
| return self.stream.label | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
||||||||||||||||||
| class DataFrameSource(Source): | ||||||||||||||||||
| """ | ||||||||||||||||||
| A stream source that sources data from a pandas DataFrame. | ||||||||||||||||||
|
|
||||||||||||||||||
| For each row in the DataFrame, yields a tuple containing: | ||||||||||||||||||
| - A tag generated either by the provided tag_function or defaulting to the row index | ||||||||||||||||||
| - A packet containing values from specified columns as key-value pairs | ||||||||||||||||||
|
|
||||||||||||||||||
| Parameters | ||||||||||||||||||
| ---------- | ||||||||||||||||||
| columns : list[str] | ||||||||||||||||||
| List of column names to include in the packet. These will serve as the keys | ||||||||||||||||||
| in the packet, with the corresponding row values as the packet values. | ||||||||||||||||||
| data : pd.DataFrame | ||||||||||||||||||
| The pandas DataFrame to source data from | ||||||||||||||||||
| tag_function : Callable[[pd.Series, int], Tag] | None, default=None | ||||||||||||||||||
| Optional function to generate a tag from a DataFrame row and its index. | ||||||||||||||||||
| The function receives the row as a pandas Series and the row index as arguments. | ||||||||||||||||||
| If None, uses the row index in a dict with key 'row_index' | ||||||||||||||||||
| tag_function_hash_mode : Literal["content", "signature", "name"], default="name" | ||||||||||||||||||
| How to hash the tag function for identity purposes | ||||||||||||||||||
| expected_tag_keys : Collection[str] | None, default=None | ||||||||||||||||||
| Expected tag keys for the stream | ||||||||||||||||||
| label : str | None, default=None | ||||||||||||||||||
| Optional label for the source | ||||||||||||||||||
|
|
||||||||||||||||||
| Examples | ||||||||||||||||||
| -------- | ||||||||||||||||||
| >>> import pandas as pd | ||||||||||||||||||
| >>> df = pd.DataFrame({ | ||||||||||||||||||
| ... 'file_path': ['/path/to/file1.txt', '/path/to/file2.txt'], | ||||||||||||||||||
| ... 'metadata_path': ['/path/to/meta1.json', '/path/to/meta2.json'], | ||||||||||||||||||
| ... 'sample_id': ['sample_1', 'sample_2'] | ||||||||||||||||||
| ... }) | ||||||||||||||||||
| >>> # Use sample_id column for tags and include file paths in packets | ||||||||||||||||||
| >>> source = DataFrameSource( | ||||||||||||||||||
| ... columns=['file_path', 'metadata_path'], | ||||||||||||||||||
| ... data=df, | ||||||||||||||||||
| ... tag_function=lambda row, idx: {'sample_id': row['sample_id']} | ||||||||||||||||||
| ... ) | ||||||||||||||||||
| >>> # Use default row index tagging | ||||||||||||||||||
| >>> source = DataFrameSource(['file_path', 'metadata_path'], df) | ||||||||||||||||||
| """ | ||||||||||||||||||
|
|
||||||||||||||||||
| @staticmethod | ||||||||||||||||||
| def default_tag_function(row: pd.Series, idx: int) -> Tag: | ||||||||||||||||||
| return {"row_index": idx} | ||||||||||||||||||
|
|
||||||||||||||||||
| def __init__( | ||||||||||||||||||
| self, | ||||||||||||||||||
| columns: list[str], | ||||||||||||||||||
| data: pd.DataFrame, | ||||||||||||||||||
| tag_function: Callable[[pd.Series, int], Tag] | None = None, | ||||||||||||||||||
| label: str | None = None, | ||||||||||||||||||
| tag_function_hash_mode: Literal["content", "signature", "name"] = "name", | ||||||||||||||||||
| expected_tag_keys: Collection[str] | None = None, | ||||||||||||||||||
| **kwargs, | ||||||||||||||||||
| ) -> None: | ||||||||||||||||||
| super().__init__(label=label, **kwargs) | ||||||||||||||||||
| self.columns = columns | ||||||||||||||||||
| self.dataframe = data | ||||||||||||||||||
|
|
||||||||||||||||||
| # Validate that all specified columns exist in the DataFrame | ||||||||||||||||||
| missing_columns = set(columns) - set(data.columns) | ||||||||||||||||||
| if missing_columns: | ||||||||||||||||||
| raise ValueError(f"Columns not found in DataFrame: {missing_columns}") | ||||||||||||||||||
|
|
||||||||||||||||||
| if tag_function is None: | ||||||||||||||||||
|
||||||||||||||||||
| tag_function = self.__class__.default_tag_function | ||||||||||||||||||
| # If using default tag function and no explicit expected_tag_keys, set to default | ||||||||||||||||||
| if expected_tag_keys is None: | ||||||||||||||||||
| expected_tag_keys = ["row_index"] | ||||||||||||||||||
|
|
||||||||||||||||||
| self.expected_tag_keys = expected_tag_keys | ||||||||||||||||||
| self.tag_function = tag_function | ||||||||||||||||||
| self.tag_function_hash_mode = tag_function_hash_mode | ||||||||||||||||||
|
|
||||||||||||||||||
| def forward(self, *streams: SyncStream) -> SyncStream: | ||||||||||||||||||
| if len(streams) != 0: | ||||||||||||||||||
| raise ValueError( | ||||||||||||||||||
| "DataFrameSource does not support forwarding streams. " | ||||||||||||||||||
| "It generates its own stream from the DataFrame." | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| def generator() -> Iterator[tuple[Tag, Packet]]: | ||||||||||||||||||
| for idx, row in self.dataframe.iterrows(): | ||||||||||||||||||
| tag = self.tag_function(row, idx) | ||||||||||||||||||
| packet = {col: row[col] for col in self.columns} | ||||||||||||||||||
|
Comment on lines
+292
to
+294
|
||||||||||||||||||
| for idx, row in self.dataframe.iterrows(): | |
| tag = self.tag_function(row, idx) | |
| packet = {col: row[col] for col in self.columns} | |
| for row in self.dataframe.itertuples(index=True, name=None): | |
| idx, *values = row | |
| row_series = pd.Series(values, index=self.dataframe.columns) | |
| tag = self.tag_function(row_series, idx) | |
| packet = {col: row_series[col] for col in self.columns} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The import 'polars as pl' is not used in this file. Consider removing it to keep imports clean.