|
17 | 17 | ) |
18 | 18 | from feast.offline_server import OfflineServer, _init_auth_manager |
19 | 19 | from feast.repo_config import RepoConfig |
| 20 | +from feast.torch_wrapper import get_torch |
20 | 21 | from tests.utils.cli_repo_creator import CliRunner |
21 | 22 |
|
22 | 23 | PROJECT_NAME = "test_remote_offline" |
@@ -115,7 +116,9 @@ def test_remote_offline_store_apis(): |
115 | 116 | fs = remote_feature_store(server) |
116 | 117 |
|
117 | 118 | _test_get_historical_features_returns_data(fs) |
| 119 | + _test_get_historical_features_to_tensor(fs) |
118 | 120 | _test_get_historical_features_returns_nan(fs) |
| 121 | + _test_get_historical_features_to_tensor_with_nan(fs) |
119 | 122 | _test_offline_write_batch(str(temp_dir), fs) |
120 | 123 | _test_write_logged_features(str(temp_dir), fs) |
121 | 124 | _test_pull_latest_from_table_or_query(str(temp_dir), fs) |
@@ -187,6 +190,44 @@ def _test_get_historical_features_returns_data(fs: FeatureStore): |
187 | 190 | assertpy.assert_that(value).is_not_nan() |
188 | 191 |
|
189 | 192 |
|
| 193 | +def _test_get_historical_features_to_tensor(fs: FeatureStore): |
| 194 | + entity_df = pd.DataFrame.from_dict( |
| 195 | + { |
| 196 | + "driver_id": [1001, 1002, 1003], |
| 197 | + "event_timestamp": [ |
| 198 | + datetime(2021, 4, 12, 10, 59, 42), |
| 199 | + datetime(2021, 4, 12, 8, 12, 10), |
| 200 | + datetime(2021, 4, 12, 16, 40, 26), |
| 201 | + ], |
| 202 | + "label_driver_reported_satisfaction": [1, 5, 3], |
| 203 | + "val_to_add": [1, 2, 3], |
| 204 | + "val_to_add_2": [10, 20, 30], |
| 205 | + } |
| 206 | + ) |
| 207 | + |
| 208 | + features = [ |
| 209 | + "driver_hourly_stats:conv_rate", |
| 210 | + "driver_hourly_stats:acc_rate", |
| 211 | + "driver_hourly_stats:avg_daily_trips", |
| 212 | + "transformed_conv_rate:conv_rate_plus_val1", |
| 213 | + "transformed_conv_rate:conv_rate_plus_val2", |
| 214 | + ] |
| 215 | + |
| 216 | + job = fs.get_historical_features(entity_df, features) |
| 217 | + tensor_data = job.to_tensor() |
| 218 | + |
| 219 | + assertpy.assert_that(tensor_data).is_not_none() |
| 220 | + assertpy.assert_that(tensor_data["driver_id"].shape[0]).is_equal_to(3) |
| 221 | + torch = get_torch() |
| 222 | + for key, values in tensor_data.items(): |
| 223 | + if isinstance(values, torch.Tensor): |
| 224 | + assertpy.assert_that(values.shape[0]).is_equal_to(3) |
| 225 | + for val in values: |
| 226 | + val_float = val.item() |
| 227 | + assertpy.assert_that(val_float).is_instance_of((float, int)) |
| 228 | + assertpy.assert_that(val_float).is_not_nan() |
| 229 | + |
| 230 | + |
190 | 231 | def _test_get_historical_features_returns_nan(fs: FeatureStore): |
191 | 232 | entity_df = pd.DataFrame.from_dict( |
192 | 233 | { |
@@ -223,6 +264,28 @@ def _test_get_historical_features_returns_nan(fs: FeatureStore): |
223 | 264 | assertpy.assert_that(value).is_nan() |
224 | 265 |
|
225 | 266 |
|
| 267 | +def _test_get_historical_features_to_tensor_with_nan(fs: FeatureStore): |
| 268 | + entity_df = pd.DataFrame.from_dict( |
| 269 | + { |
| 270 | + "driver_id": [9991, 9992], # IDs with no matching features |
| 271 | + "event_timestamp": [ |
| 272 | + datetime(2021, 4, 12, 10, 59, 42), |
| 273 | + datetime(2021, 4, 12, 10, 59, 42), |
| 274 | + ], |
| 275 | + } |
| 276 | + ) |
| 277 | + features = ["driver_hourly_stats:conv_rate"] |
| 278 | + job = fs.get_historical_features(entity_df, features) |
| 279 | + tensor_data = job.to_tensor() |
| 280 | + assert "conv_rate" in tensor_data |
| 281 | + values = tensor_data["conv_rate"] |
| 282 | + # conv_rate is a float feature, missing values should be NaN |
| 283 | + torch = get_torch() |
| 284 | + for val in values: |
| 285 | + assert isinstance(val, torch.Tensor) or torch.is_tensor(val) |
| 286 | + assertpy.assert_that(torch.isnan(val).item()).is_true() |
| 287 | + |
| 288 | + |
226 | 289 | def _test_offline_write_batch(temp_dir, fs: FeatureStore): |
227 | 290 | data_file = os.path.join( |
228 | 291 | temp_dir, fs.project, "feature_repo/data/driver_stats.parquet" |
|
0 commit comments