easy_ml 0.1.4 → 0.2.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (239) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +234 -26
  3. data/Rakefile +45 -0
  4. data/app/controllers/easy_ml/application_controller.rb +67 -0
  5. data/app/controllers/easy_ml/columns_controller.rb +38 -0
  6. data/app/controllers/easy_ml/datasets_controller.rb +156 -0
  7. data/app/controllers/easy_ml/datasources_controller.rb +88 -0
  8. data/app/controllers/easy_ml/deploys_controller.rb +20 -0
  9. data/app/controllers/easy_ml/models_controller.rb +151 -0
  10. data/app/controllers/easy_ml/retraining_runs_controller.rb +19 -0
  11. data/app/controllers/easy_ml/settings_controller.rb +59 -0
  12. data/app/frontend/components/AlertProvider.tsx +108 -0
  13. data/app/frontend/components/DatasetPreview.tsx +161 -0
  14. data/app/frontend/components/EmptyState.tsx +28 -0
  15. data/app/frontend/components/ModelCard.tsx +255 -0
  16. data/app/frontend/components/ModelDetails.tsx +334 -0
  17. data/app/frontend/components/ModelForm.tsx +384 -0
  18. data/app/frontend/components/Navigation.tsx +300 -0
  19. data/app/frontend/components/Pagination.tsx +72 -0
  20. data/app/frontend/components/Popover.tsx +55 -0
  21. data/app/frontend/components/PredictionStream.tsx +105 -0
  22. data/app/frontend/components/ScheduleModal.tsx +726 -0
  23. data/app/frontend/components/SearchInput.tsx +23 -0
  24. data/app/frontend/components/SearchableSelect.tsx +132 -0
  25. data/app/frontend/components/dataset/AutosaveIndicator.tsx +39 -0
  26. data/app/frontend/components/dataset/ColumnConfigModal.tsx +431 -0
  27. data/app/frontend/components/dataset/ColumnFilters.tsx +256 -0
  28. data/app/frontend/components/dataset/ColumnList.tsx +101 -0
  29. data/app/frontend/components/dataset/FeatureConfigPopover.tsx +57 -0
  30. data/app/frontend/components/dataset/FeaturePicker.tsx +205 -0
  31. data/app/frontend/components/dataset/PreprocessingConfig.tsx +704 -0
  32. data/app/frontend/components/dataset/SplitConfigurator.tsx +120 -0
  33. data/app/frontend/components/dataset/splitters/DateSplitter.tsx +58 -0
  34. data/app/frontend/components/dataset/splitters/KFoldSplitter.tsx +68 -0
  35. data/app/frontend/components/dataset/splitters/LeavePOutSplitter.tsx +29 -0
  36. data/app/frontend/components/dataset/splitters/PredefinedSplitter.tsx +146 -0
  37. data/app/frontend/components/dataset/splitters/RandomSplitter.tsx +85 -0
  38. data/app/frontend/components/dataset/splitters/StratifiedSplitter.tsx +79 -0
  39. data/app/frontend/components/dataset/splitters/constants.ts +77 -0
  40. data/app/frontend/components/dataset/splitters/types.ts +168 -0
  41. data/app/frontend/components/dataset/splitters/utils.ts +53 -0
  42. data/app/frontend/components/features/CodeEditor.tsx +46 -0
  43. data/app/frontend/components/features/DataPreview.tsx +150 -0
  44. data/app/frontend/components/features/FeatureCard.tsx +88 -0
  45. data/app/frontend/components/features/FeatureForm.tsx +235 -0
  46. data/app/frontend/components/features/FeatureGroupCard.tsx +54 -0
  47. data/app/frontend/components/settings/PluginSettings.tsx +81 -0
  48. data/app/frontend/components/ui/badge.tsx +44 -0
  49. data/app/frontend/components/ui/collapsible.tsx +9 -0
  50. data/app/frontend/components/ui/scroll-area.tsx +46 -0
  51. data/app/frontend/components/ui/separator.tsx +29 -0
  52. data/app/frontend/entrypoints/App.tsx +40 -0
  53. data/app/frontend/entrypoints/Application.tsx +24 -0
  54. data/app/frontend/hooks/useAutosave.ts +61 -0
  55. data/app/frontend/layouts/Layout.tsx +38 -0
  56. data/app/frontend/lib/utils.ts +6 -0
  57. data/app/frontend/mockData.ts +272 -0
  58. data/app/frontend/pages/DatasetDetailsPage.tsx +103 -0
  59. data/app/frontend/pages/DatasetsPage.tsx +261 -0
  60. data/app/frontend/pages/DatasourceFormPage.tsx +147 -0
  61. data/app/frontend/pages/DatasourcesPage.tsx +261 -0
  62. data/app/frontend/pages/EditModelPage.tsx +45 -0
  63. data/app/frontend/pages/EditTransformationPage.tsx +56 -0
  64. data/app/frontend/pages/ModelsPage.tsx +115 -0
  65. data/app/frontend/pages/NewDatasetPage.tsx +366 -0
  66. data/app/frontend/pages/NewModelPage.tsx +45 -0
  67. data/app/frontend/pages/NewTransformationPage.tsx +43 -0
  68. data/app/frontend/pages/SettingsPage.tsx +272 -0
  69. data/app/frontend/pages/ShowModelPage.tsx +30 -0
  70. data/app/frontend/pages/TransformationsPage.tsx +95 -0
  71. data/app/frontend/styles/application.css +100 -0
  72. data/app/frontend/types/dataset.ts +146 -0
  73. data/app/frontend/types/datasource.ts +33 -0
  74. data/app/frontend/types/preprocessing.ts +1 -0
  75. data/app/frontend/types.ts +113 -0
  76. data/app/helpers/easy_ml/application_helper.rb +10 -0
  77. data/app/jobs/easy_ml/application_job.rb +21 -0
  78. data/app/jobs/easy_ml/batch_job.rb +46 -0
  79. data/app/jobs/easy_ml/compute_feature_job.rb +19 -0
  80. data/app/jobs/easy_ml/deploy_job.rb +13 -0
  81. data/app/jobs/easy_ml/finalize_feature_job.rb +15 -0
  82. data/app/jobs/easy_ml/refresh_dataset_job.rb +32 -0
  83. data/app/jobs/easy_ml/schedule_retraining_job.rb +11 -0
  84. data/app/jobs/easy_ml/sync_datasource_job.rb +17 -0
  85. data/app/jobs/easy_ml/training_job.rb +62 -0
  86. data/app/models/easy_ml/adapters/base_adapter.rb +45 -0
  87. data/app/models/easy_ml/adapters/polars_adapter.rb +77 -0
  88. data/app/models/easy_ml/cleaner.rb +82 -0
  89. data/app/models/easy_ml/column.rb +124 -0
  90. data/app/models/easy_ml/column_history.rb +30 -0
  91. data/app/models/easy_ml/column_list.rb +122 -0
  92. data/app/models/easy_ml/concerns/configurable.rb +61 -0
  93. data/app/models/easy_ml/concerns/versionable.rb +19 -0
  94. data/app/models/easy_ml/dataset.rb +767 -0
  95. data/app/models/easy_ml/dataset_history.rb +56 -0
  96. data/app/models/easy_ml/datasource.rb +182 -0
  97. data/app/models/easy_ml/datasource_history.rb +24 -0
  98. data/app/models/easy_ml/datasources/base_datasource.rb +54 -0
  99. data/app/models/easy_ml/datasources/file_datasource.rb +58 -0
  100. data/app/models/easy_ml/datasources/polars_datasource.rb +89 -0
  101. data/app/models/easy_ml/datasources/s3_datasource.rb +97 -0
  102. data/app/models/easy_ml/deploy.rb +114 -0
  103. data/app/models/easy_ml/event.rb +79 -0
  104. data/app/models/easy_ml/feature.rb +437 -0
  105. data/app/models/easy_ml/feature_history.rb +38 -0
  106. data/app/models/easy_ml/model.rb +575 -41
  107. data/app/models/easy_ml/model_file.rb +133 -0
  108. data/app/models/easy_ml/model_file_history.rb +24 -0
  109. data/app/models/easy_ml/model_history.rb +51 -0
  110. data/app/models/easy_ml/models/base_model.rb +58 -0
  111. data/app/models/easy_ml/models/hyperparameters/base.rb +99 -0
  112. data/app/models/easy_ml/models/hyperparameters/xgboost/dart.rb +82 -0
  113. data/app/models/easy_ml/models/hyperparameters/xgboost/gblinear.rb +82 -0
  114. data/app/models/easy_ml/models/hyperparameters/xgboost/gbtree.rb +97 -0
  115. data/app/models/easy_ml/models/hyperparameters/xgboost.rb +71 -0
  116. data/app/models/easy_ml/models/xgboost/evals_callback.rb +138 -0
  117. data/app/models/easy_ml/models/xgboost/progress_callback.rb +39 -0
  118. data/app/models/easy_ml/models/xgboost.rb +544 -5
  119. data/app/models/easy_ml/prediction.rb +44 -0
  120. data/app/models/easy_ml/retraining_job.rb +278 -0
  121. data/app/models/easy_ml/retraining_run.rb +184 -0
  122. data/app/models/easy_ml/settings.rb +37 -0
  123. data/app/models/easy_ml/splitter.rb +90 -0
  124. data/app/models/easy_ml/splitters/base_splitter.rb +28 -0
  125. data/app/models/easy_ml/splitters/date_splitter.rb +91 -0
  126. data/app/models/easy_ml/splitters/predefined_splitter.rb +74 -0
  127. data/app/models/easy_ml/splitters/random_splitter.rb +82 -0
  128. data/app/models/easy_ml/tuner_job.rb +56 -0
  129. data/app/models/easy_ml/tuner_run.rb +31 -0
  130. data/app/models/splitter_history.rb +6 -0
  131. data/app/serializers/easy_ml/column_serializer.rb +27 -0
  132. data/app/serializers/easy_ml/dataset_serializer.rb +73 -0
  133. data/app/serializers/easy_ml/datasource_serializer.rb +64 -0
  134. data/app/serializers/easy_ml/feature_serializer.rb +27 -0
  135. data/app/serializers/easy_ml/model_serializer.rb +90 -0
  136. data/app/serializers/easy_ml/retraining_job_serializer.rb +22 -0
  137. data/app/serializers/easy_ml/retraining_run_serializer.rb +39 -0
  138. data/app/serializers/easy_ml/settings_serializer.rb +9 -0
  139. data/app/views/layouts/easy_ml/application.html.erb +15 -0
  140. data/config/initializers/resque.rb +3 -0
  141. data/config/resque-pool.yml +6 -0
  142. data/config/routes.rb +39 -0
  143. data/config/spring.rb +1 -0
  144. data/config/vite.json +15 -0
  145. data/lib/easy_ml/configuration.rb +64 -0
  146. data/lib/easy_ml/core/evaluators/base_evaluator.rb +53 -0
  147. data/lib/easy_ml/core/evaluators/classification_evaluators.rb +126 -0
  148. data/lib/easy_ml/core/evaluators/regression_evaluators.rb +66 -0
  149. data/lib/easy_ml/core/model_evaluator.rb +161 -89
  150. data/lib/easy_ml/core/tuner/adapters/base_adapter.rb +28 -18
  151. data/lib/easy_ml/core/tuner/adapters/xgboost_adapter.rb +4 -25
  152. data/lib/easy_ml/core/tuner.rb +123 -62
  153. data/lib/easy_ml/core.rb +0 -3
  154. data/lib/easy_ml/core_ext/hash.rb +24 -0
  155. data/lib/easy_ml/core_ext/pathname.rb +11 -5
  156. data/lib/easy_ml/data/date_converter.rb +90 -0
  157. data/lib/easy_ml/data/filter_extensions.rb +31 -0
  158. data/lib/easy_ml/data/polars_column.rb +126 -0
  159. data/lib/easy_ml/data/polars_reader.rb +297 -0
  160. data/lib/easy_ml/data/preprocessor.rb +280 -142
  161. data/lib/easy_ml/data/simple_imputer.rb +255 -0
  162. data/lib/easy_ml/data/splits/file_split.rb +252 -0
  163. data/lib/easy_ml/data/splits/in_memory_split.rb +54 -0
  164. data/lib/easy_ml/data/splits/split.rb +95 -0
  165. data/lib/easy_ml/data/splits.rb +9 -0
  166. data/lib/easy_ml/data/statistics_learner.rb +93 -0
  167. data/lib/easy_ml/data/synced_directory.rb +341 -0
  168. data/lib/easy_ml/data.rb +6 -2
  169. data/lib/easy_ml/engine.rb +105 -6
  170. data/lib/easy_ml/feature_store.rb +227 -0
  171. data/lib/easy_ml/features.rb +61 -0
  172. data/lib/easy_ml/initializers/inflections.rb +17 -3
  173. data/lib/easy_ml/logging.rb +2 -2
  174. data/lib/easy_ml/predict.rb +74 -0
  175. data/lib/easy_ml/railtie/generators/migration/migration_generator.rb +192 -36
  176. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_column_histories.rb.tt +9 -0
  177. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_columns.rb.tt +25 -0
  178. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_dataset_histories.rb.tt +9 -0
  179. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasets.rb.tt +31 -0
  180. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasource_histories.rb.tt +9 -0
  181. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasources.rb.tt +16 -0
  182. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_deploys.rb.tt +24 -0
  183. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_events.rb.tt +20 -0
  184. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_feature_histories.rb.tt +14 -0
  185. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_features.rb.tt +32 -0
  186. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_file_histories.rb.tt +9 -0
  187. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_files.rb.tt +17 -0
  188. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_histories.rb.tt +9 -0
  189. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_models.rb.tt +20 -9
  190. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_predictions.rb.tt +17 -0
  191. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_retraining_jobs.rb.tt +77 -0
  192. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_settings.rb.tt +9 -0
  193. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_splitter_histories.rb.tt +9 -0
  194. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_splitters.rb.tt +15 -0
  195. data/lib/easy_ml/railtie/templates/migration/create_easy_ml_tuner_jobs.rb.tt +40 -0
  196. data/lib/easy_ml/support/est.rb +5 -1
  197. data/lib/easy_ml/support/file_rotate.rb +79 -15
  198. data/lib/easy_ml/support/file_support.rb +9 -0
  199. data/lib/easy_ml/support/local_file.rb +24 -0
  200. data/lib/easy_ml/support/lockable.rb +62 -0
  201. data/lib/easy_ml/support/synced_file.rb +103 -0
  202. data/lib/easy_ml/support/utc.rb +5 -1
  203. data/lib/easy_ml/support.rb +6 -3
  204. data/lib/easy_ml/version.rb +4 -1
  205. data/lib/easy_ml.rb +7 -2
  206. metadata +355 -72
  207. data/app/models/easy_ml/models.rb +0 -5
  208. data/lib/easy_ml/core/model.rb +0 -30
  209. data/lib/easy_ml/core/model_core.rb +0 -181
  210. data/lib/easy_ml/core/models/hyperparameters/base.rb +0 -34
  211. data/lib/easy_ml/core/models/hyperparameters/xgboost.rb +0 -19
  212. data/lib/easy_ml/core/models/xgboost.rb +0 -10
  213. data/lib/easy_ml/core/models/xgboost_core.rb +0 -220
  214. data/lib/easy_ml/core/models.rb +0 -10
  215. data/lib/easy_ml/core/uploaders/model_uploader.rb +0 -24
  216. data/lib/easy_ml/core/uploaders.rb +0 -7
  217. data/lib/easy_ml/data/dataloader.rb +0 -6
  218. data/lib/easy_ml/data/dataset/data/preprocessor/statistics.json +0 -31
  219. data/lib/easy_ml/data/dataset/data/sample_info.json +0 -1
  220. data/lib/easy_ml/data/dataset/dataset/files/sample_info.json +0 -1
  221. data/lib/easy_ml/data/dataset/splits/file_split.rb +0 -140
  222. data/lib/easy_ml/data/dataset/splits/in_memory_split.rb +0 -49
  223. data/lib/easy_ml/data/dataset/splits/split.rb +0 -98
  224. data/lib/easy_ml/data/dataset/splits.rb +0 -11
  225. data/lib/easy_ml/data/dataset/splitters/date_splitter.rb +0 -43
  226. data/lib/easy_ml/data/dataset/splitters.rb +0 -9
  227. data/lib/easy_ml/data/dataset.rb +0 -430
  228. data/lib/easy_ml/data/datasource/datasource_factory.rb +0 -60
  229. data/lib/easy_ml/data/datasource/file_datasource.rb +0 -40
  230. data/lib/easy_ml/data/datasource/merged_datasource.rb +0 -64
  231. data/lib/easy_ml/data/datasource/polars_datasource.rb +0 -41
  232. data/lib/easy_ml/data/datasource/s3_datasource.rb +0 -89
  233. data/lib/easy_ml/data/datasource.rb +0 -33
  234. data/lib/easy_ml/data/preprocessor/preprocessor.rb +0 -205
  235. data/lib/easy_ml/data/preprocessor/simple_imputer.rb +0 -402
  236. data/lib/easy_ml/deployment.rb +0 -5
  237. data/lib/easy_ml/support/synced_directory.rb +0 -134
  238. data/lib/easy_ml/transforms.rb +0 -29
  239. /data/{lib/easy_ml/core → app/models/easy_ml}/models/hyperparameters.rb +0 -0
@@ -1,40 +0,0 @@
1
- module EasyML::Data
2
- class Datasource
3
- class FileDatasource < Datasource
4
- include GlueGun::DSL
5
-
6
- attribute :root_dir, :string
7
- attribute :polars_args, :hash, default: {}
8
-
9
- validates :root_dir, presence: true
10
-
11
- def in_batches(of: 10_000)
12
- files.each do |file|
13
- df = Polars.read_csv(file, **polars_args)
14
- yield df
15
- end
16
- end
17
-
18
- def files
19
- Dir.glob(File.join(root_dir, "**/*.csv")).sort
20
- end
21
-
22
- def last_updated_at
23
- files.map { |file| File.mtime(file) }.max
24
- end
25
-
26
- def refresh!
27
- # No need to refresh for directory-based datasource
28
- end
29
-
30
- def data
31
- combined_df = nil
32
- files.each do |file|
33
- df = Polars.read_csv(file, **polars_args)
34
- combined_df = combined_df.nil? ? df : combined_df.vstack(df)
35
- end
36
- combined_df
37
- end
38
- end
39
- end
40
- end
@@ -1,64 +0,0 @@
1
- module EasyML::Data
2
- class Datasource
3
- class MergedDatasource < Datasource
4
- include GlueGun::DSL
5
-
6
- attribute :root_dir, :string
7
- attribute :polars_args, :hash, default: {}
8
- attribute :merge
9
- validates :root_dir, presence: true
10
- validates :merge, presence: true
11
-
12
- def in_batches(of: 10_000, &block)
13
- Polars.read_csv(file_path, **polars_args).iter_batches(batch_size: of, &block)
14
- end
15
-
16
- def file_path
17
- @file_path ||= File.join(root_dir, "merged_data.csv")
18
- end
19
-
20
- def last_updated_at
21
- datasources.map(&:last_updated_at).min
22
- end
23
-
24
- def refresh!
25
- cleanup
26
- if datasources.is_a?(Array)
27
- datasources.each(&:refresh!)
28
- elsif datasources.is_a?(Hash)
29
- datasources.values.each(&:refresh!)
30
- end
31
- end
32
-
33
- def data
34
- @data ||= if file_exists?
35
- Polars.read_csv(file_path, **polars_args)
36
- else
37
- merge_and_save
38
- end
39
- end
40
-
41
- def cleanup
42
- FileUtils.rm_f(file_path)
43
- end
44
-
45
- private
46
-
47
- def file_exists?
48
- File.exist?(file_path)
49
- end
50
-
51
- def merge_and_save
52
- refresh!
53
- merge.call(datasources).tap do |merged_data|
54
- save_to_file(merged_data)
55
- end
56
- end
57
-
58
- def save_to_file(df)
59
- FileUtils.mkdir_p(root_dir)
60
- df.write_csv(file_path)
61
- end
62
- end
63
- end
64
- end
@@ -1,41 +0,0 @@
1
- module EasyML::Data
2
- class Datasource
3
- class PolarsDatasource < Datasource
4
- include GlueGun::DSL
5
-
6
- attribute :df
7
- validate :df_is_dataframe
8
- def df_is_dataframe
9
- return if df.nil? || df.is_a?(Polars::DataFrame)
10
-
11
- errors.add(:df, "Must be an instance of Polars::DataFrame")
12
- end
13
- attr_accessor :last_updated_at
14
-
15
- def initialize(options)
16
- super
17
- @last_updated_at = Time.now
18
- end
19
-
20
- def in_batches(of: 10_000)
21
- total_rows = df.shape[0]
22
- (0...total_rows).step(of) do |start|
23
- end_index = [start + of, total_rows].min
24
- yield df.slice(start, end_index - start)
25
- end
26
- end
27
-
28
- def files
29
- [] # No files, as this is in-memory
30
- end
31
-
32
- def refresh!
33
- # No need to refresh for in-memory datasource
34
- end
35
-
36
- def data
37
- df
38
- end
39
- end
40
- end
41
- end
@@ -1,89 +0,0 @@
1
- require "polars"
2
-
3
- module EasyML::Data
4
- class Datasource
5
- class S3Datasource
6
- include GlueGun::DSL
7
-
8
- attribute :root_dir, :string
9
- validates :root_dir, presence: true
10
-
11
- attribute :polars_args, :hash, default: {}
12
- validates :polars_args, presence: true
13
-
14
- attribute :s3_bucket, :string
15
- validates :s3_bucket, presence: true
16
-
17
- attribute :s3_prefix, :string
18
- validates :s3_prefix, presence: true
19
- def s3_prefix=(arg)
20
- super(arg.to_s.gsub(%r{^/|/$}, ""))
21
- end
22
-
23
- attribute :s3_access_key_id, :string
24
- validates :s3_access_key_id, presence: true
25
-
26
- attribute :s3_secret_access_key, :string
27
- validates :s3_secret_access_key, presence: true
28
-
29
- dependency :synced_directory do |dependency|
30
- dependency.set_class EasyML::Support::SyncedDirectory
31
- dependency.bind_attribute :root_dir, required: true
32
- dependency.bind_attribute :s3_bucket, required: true
33
- dependency.bind_attribute :s3_prefix
34
- dependency.bind_attribute :s3_access_key_id, required: true
35
- dependency.bind_attribute :s3_secret_access_key, required: true
36
- end
37
-
38
- delegate :files, :last_updated_at, to: :synced_directory
39
-
40
- def in_batches(of: 10_000)
41
- # Currently ignores batch size, TODO: implement
42
- pull
43
- files.each do |file|
44
- csv = Polars.read_csv(file, **polars_args)
45
- yield csv
46
- end
47
- end
48
-
49
- def refresh!
50
- synced_directory.sync
51
- end
52
-
53
- def data
54
- pull do |did_sync|
55
- output_path = File.join(root_dir, "combined_data.csv")
56
-
57
- if did_sync
58
- combined_df = merge_data
59
- combined_df.write_csv(output_path)
60
- else
61
- Polars.read_csv(output_path, **polars_args)
62
- end
63
- end
64
- combined_df
65
- end
66
-
67
- private
68
-
69
- def pull
70
- # Synced directory will only sync if needs sync
71
- did_sync = synced_directory.sync
72
- yield did_sync if block_given?
73
- end
74
-
75
- def merge_data
76
- combined_df = nil
77
- files.each do |file|
78
- df = Polars.read_csv(file, **polars_args)
79
- combined_df = if combined_df.nil?
80
- df
81
- else
82
- combined_df.vstack(df)
83
- end
84
- end
85
- combined_df
86
- end
87
- end
88
- end
89
- end
@@ -1,33 +0,0 @@
1
- module ML
2
- module Data
3
- class Datasource
4
- attr_reader :root_dir, :polars_args
5
-
6
- def in_batches(of: 10_000)
7
- raise NotImplementedError, "Subclasses must implement #in_batches"
8
- end
9
-
10
- def files
11
- raise NotImplementedError, "Subclasses must implement #files"
12
- end
13
-
14
- def last_updated_at
15
- raise NotImplementedError, "Subclasses must implement #last_updated_at"
16
- end
17
-
18
- def refresh!
19
- raise NotImplementedError, "Subclasses must implement #refresh!"
20
- end
21
-
22
- def data
23
- raise NotImplementedError, "Subclasses must implement #data"
24
- end
25
-
26
- require_relative "datasource/s3_datasource"
27
- require_relative "datasource/file_datasource"
28
- require_relative "datasource/polars_datasource"
29
- require_relative "datasource/merged_datasource"
30
- require_relative "datasource/datasource_factory"
31
- end
32
- end
33
- end
@@ -1,205 +0,0 @@
1
- require "fileutils"
2
- require "polars"
3
- require "date"
4
- require "json"
5
-
6
- module EasyML::Data
7
- class PreprocessingSteps
8
- class Preprocessor
9
- include EasyML::Data::PreprocessingSteps::Utils
10
-
11
- CATEGORICAL_COMMON_MIN = 50
12
- PREPROCESSING_ORDER = %w[clip mean median constant categorical one_hot ffill custom fill_date add_datepart]
13
-
14
- attr_accessor :directory, :preprocessing_steps, :verbose, :imputers, :environment
15
-
16
- def initialize(directory: nil, preprocessing_steps: {}, verbose: false, environment: "development")
17
- @directory = directory
18
- @preprocessing_steps = standardize_config(preprocessing_steps).with_indifferent_access
19
- @verbose = verbose
20
- @environment = environment
21
- end
22
-
23
- def fit(df)
24
- return if df.nil?
25
- return if preprocessing_steps.keys.none?
26
-
27
- puts "Preprocessing..." if verbose
28
- imputers = initialize_imputers(
29
- preprocessing_steps[:training].merge!(preprocessing_steps[:inference] || {})
30
- )
31
-
32
- did_cleanup = false
33
- imputers.each do |col, imputers|
34
- sorted_strategies(imputers).each do |strategy|
35
- imputer = imputers[strategy]
36
- unless did_cleanup
37
- imputer.cleanup
38
- did_cleanup = true
39
- end
40
- if df.columns.map(&:downcase).include?(col.downcase)
41
- actual_col = df.columns.find { |c| c.downcase == imputer.attribute.downcase }
42
- imputer.fit(df[actual_col], df)
43
- if strategy == "clip" # This is the only one to transform during fit
44
- df[actual_col] = imputer.transform(df[actual_col])
45
- end
46
- elsif @verbose
47
- puts "Warning: Column '#{col}' not found in DataFrame during fit process."
48
- end
49
- end
50
- end
51
- end
52
-
53
- def postprocess(df, inference: false)
54
- puts "Postprocessing..." if verbose
55
- return df if preprocessing_steps.keys.none?
56
-
57
- steps = if inference
58
- preprocessing_steps[:training].merge(preprocessing_steps[:inference] || {})
59
- else
60
- preprocessing_steps[:training]
61
- end
62
-
63
- df = apply_transformations(df, steps)
64
-
65
- puts "Postprocessing complete." if @verbose
66
- df
67
- end
68
-
69
- def statistics
70
- initialize_imputers(preprocessing_steps[:training]).each_with_object({}) do |(col, strategies), result|
71
- result[col] = strategies.each_with_object({}) do |(strategy, imputer), col_result|
72
- col_result[strategy] = imputer.statistics
73
- end
74
- end
75
- end
76
-
77
- def is_fit?
78
- statistics.any? { |_, col_stats| col_stats.any? { |_, strategy_stats| strategy_stats.present? } }
79
- end
80
-
81
- def delete
82
- return unless File.directory?(@directory)
83
-
84
- FileUtils.rm_rf(@directory)
85
- end
86
-
87
- def move(to)
88
- old_dir = directory
89
- current_env = directory.split("/")[-1]
90
- new_dir = directory.gsub(Regexp.new(current_env), to)
91
-
92
- puts "Moving #{old_dir} to #{new_dir}"
93
- FileUtils.mv(old_dir, new_dir)
94
- @directory = new_dir
95
- end
96
-
97
- private
98
-
99
- def initialize_imputers(config)
100
- standardize_config(config).each_with_object({}) do |(col, strategies), hash|
101
- hash[col] ||= {}
102
- strategies.each do |strategy, options|
103
- next if strategy.to_sym == :one_hot
104
-
105
- options = {} if options == true
106
-
107
- hash[col][strategy] = EasyML::Data::PreprocessingSteps::SimpleImputer.new(
108
- strategy: strategy,
109
- path: directory,
110
- attribute: col,
111
- options: options
112
- )
113
- end
114
- end
115
- end
116
-
117
- def apply_transformations(df, config)
118
- imputers = initialize_imputers(config)
119
-
120
- standardize_config(config).each do |col, strategies|
121
- if df.columns.map(&:downcase).include?(col.downcase)
122
- actual_col = df.columns.find { |c| c.downcase == col.downcase }
123
-
124
- sorted_strategies(strategies).each do |strategy|
125
- if strategy.to_sym == :one_hot
126
- df = apply_one_hot(df, col, imputers)
127
- else
128
- imputer = imputers.dig(col, strategy)
129
- df[actual_col] = imputer.transform(df[actual_col]) if imputer
130
- end
131
- end
132
- elsif @verbose
133
- puts "Warning: Column '#{col}' not found in DataFrame during apply_transformations process."
134
- end
135
- end
136
-
137
- df
138
- end
139
-
140
- def apply_one_hot(df, col, imputers)
141
- approved_values = if (cat_imputer = imputers.dig(col, "categorical")).present?
142
- cat_imputer.statistics[:categorical][:value].select do |_k, v|
143
- v >= cat_imputer.options["categorical_min"]
144
- end.keys
145
- else
146
- df[col].uniq.to_a
147
- end
148
-
149
- # Create one-hot encoded columns
150
- approved_values.each do |value|
151
- new_col_name = "#{col}_#{value}".gsub(/-/, "_")
152
- df = df.with_column(
153
- df[col].eq(value.to_s).cast(Polars::Int64).alias(new_col_name)
154
- )
155
- end
156
-
157
- # Create 'other' column for unapproved values
158
- other_col_name = "#{col}_other"
159
- df[other_col_name] = df[col].map_elements do |value|
160
- approved_values.map(&:to_s).exclude?(value)
161
- end.cast(Polars::Int64)
162
- df.drop([col])
163
- end
164
-
165
- def sorted_strategies(strategies)
166
- strategies.keys.sort_by do |key|
167
- PREPROCESSING_ORDER.index(key)
168
- end
169
- end
170
-
171
- def prepare_for_imputation(df, col)
172
- df = df.with_column(Polars.col(col).cast(Polars::Float64))
173
- df.with_column(Polars.when(Polars.col(col).is_null).then(Float::NAN).otherwise(Polars.col(col)).alias(col))
174
- end
175
- end
176
- end
177
- end
178
-
179
- # Where to put this???
180
- #
181
- # def self.stage_required_files
182
- # required_files.each do |file|
183
- # git_add(file)
184
- # end
185
- # end
186
-
187
- # def self.git_add(path)
188
- # command = "git add #{path}"
189
- # puts command if verbose
190
- # result = `#{command}`
191
- # puts result if verbose
192
- # end
193
-
194
- # def self.set_verbose(verbose)
195
- # @verbose = verbose
196
- # end
197
-
198
- # def required_files
199
- # files = Dir.entries(@directory) - %w[. ..]
200
- # required_file_types = %w[bin]
201
-
202
- # files.select { |file| required_file_types.any? { |ext| file.include?(ext) } }.map do |file|
203
- # File.join(@directory, file)
204
- # end
205
- # end