(
entity_ds: Dataset,
feature_df: pd.DataFrame,
join_keys: List[str],
timestamp_field: str,
requested_feats: List[str],
full_feature_names: bool = False,
feature_view_name: Optional[str] = None,
original_join_keys: Optional[List[str]] = None,
)
| 327 | |
| 328 | |
| 329 | def broadcast_join( |
| 330 | entity_ds: Dataset, |
| 331 | feature_df: pd.DataFrame, |
| 332 | join_keys: List[str], |
| 333 | timestamp_field: str, |
| 334 | requested_feats: List[str], |
| 335 | full_feature_names: bool = False, |
| 336 | feature_view_name: Optional[str] = None, |
| 337 | original_join_keys: Optional[List[str]] = None, |
| 338 | ) -> Dataset: |
| 339 | import ray |
| 340 | |
| 341 | def join_batch_with_features(batch: pd.DataFrame) -> pd.DataFrame: |
| 342 | features = ray.get(feature_ref) |
| 343 | if original_join_keys: |
| 344 | feature_join_keys = original_join_keys |
| 345 | entity_join_keys = join_keys |
| 346 | else: |
| 347 | feature_join_keys = join_keys |
| 348 | entity_join_keys = join_keys |
| 349 | feature_cols = [timestamp_field] + feature_join_keys + requested_feats |
| 350 | available_feature_cols = [ |
| 351 | col for col in feature_cols if col in features.columns |
| 352 | ] |
| 353 | features_filtered = features[available_feature_cols].copy() |
| 354 | |
| 355 | batch = normalize_timestamp_columns(batch, timestamp_field, inplace=True) |
| 356 | features_filtered = normalize_timestamp_columns( |
| 357 | features_filtered, timestamp_field, inplace=True |
| 358 | ) |
| 359 | if not entity_join_keys: |
| 360 | batch_sorted = batch.sort_values(timestamp_field).reset_index(drop=True) |
| 361 | features_sorted = features_filtered.sort_values( |
| 362 | timestamp_field |
| 363 | ).reset_index(drop=True) |
| 364 | result = pd.merge_asof( |
| 365 | batch_sorted, |
| 366 | features_sorted, |
| 367 | on=timestamp_field, |
| 368 | direction="backward", |
| 369 | ) |
| 370 | else: |
| 371 | for key in entity_join_keys: |
| 372 | if key not in batch.columns: |
| 373 | batch[key] = np.nan |
| 374 | for key in feature_join_keys: |
| 375 | if key not in features_filtered.columns: |
| 376 | features_filtered[key] = np.nan |
| 377 | batch_clean = batch.dropna( |
| 378 | subset=entity_join_keys + [timestamp_field] |
| 379 | ).copy() |
| 380 | features_clean = features_filtered.dropna( |
| 381 | subset=feature_join_keys + [timestamp_field] |
| 382 | ).copy() |
| 383 | if batch_clean.empty or features_clean.empty: |
| 384 | return batch.head(0) |
| 385 | if timestamp_field in batch_clean.columns: |
| 386 | batch_sorted = batch_clean.sort_values( |
no test coverage detected