aplr 10.17.0__cp312-cp312-win_amd64.whl → 10.18.0__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aplr might be problematic. Click here for more details.

aplr/aplr.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from typing import List, Callable, Optional, Dict, Union
2
2
  import numpy as np
3
+ import pandas as pd
3
4
  import aplr_cpp
4
5
  import itertools
5
6
 
@@ -9,7 +10,134 @@ IntVector = np.ndarray
9
10
  IntMatrix = np.ndarray
10
11
 
11
12
 
12
- class APLRRegressor:
13
+ class BaseAPLR:
14
+ def _validate_X_fit_rows(self, X):
15
+ """Checks if X has enough rows to be fitted."""
16
+ if (isinstance(X, np.ndarray) and X.shape[0] < 2) or (
17
+ isinstance(X, pd.DataFrame) and len(X) < 2
18
+ ):
19
+ raise ValueError("Input X must have at least 2 rows to be fitted.")
20
+
21
+ def _common_X_preprocessing(self, X, is_fitting: bool, X_names=None):
22
+ """Common preprocessing for fit and predict."""
23
+ is_dataframe_input = isinstance(X, pd.DataFrame)
24
+
25
+ if not is_dataframe_input:
26
+ try:
27
+ X_numeric = np.array(X, dtype=np.float64)
28
+ except (ValueError, TypeError) as e:
29
+ raise TypeError(
30
+ "Input X must be numeric if not a pandas DataFrame."
31
+ ) from e
32
+ X = pd.DataFrame(X_numeric)
33
+ if is_fitting:
34
+ if X_names:
35
+ X.columns = X_names
36
+ else:
37
+ X.columns = [f"X{i}" for i in range(X.shape[1])]
38
+ elif hasattr(self, "X_names_") and len(self.X_names_) == X.shape[1]:
39
+ X.columns = self.X_names_
40
+ else: # X is already a DataFrame
41
+ X = X.copy() # Always copy to avoid modifying original
42
+ if not is_fitting and hasattr(self, "X_names_"):
43
+ # Check if input columns for prediction match training columns (before OHE)
44
+ if set(X.columns) != set(self.X_names_):
45
+ raise ValueError(
46
+ "Input columns for prediction do not match training columns."
47
+ )
48
+ X = X[self.X_names_] # Ensure order of original columns
49
+
50
+ if is_fitting:
51
+ self.X_names_ = list(X.columns)
52
+ self.categorical_features_ = list(
53
+ X.select_dtypes(include=["category", "object"]).columns
54
+ )
55
+
56
+ if self.categorical_features_:
57
+ X = pd.get_dummies(X, columns=self.categorical_features_, dummy_na=False)
58
+ if is_fitting:
59
+ self.ohe_columns_ = list(X.columns)
60
+ else:
61
+ missing_cols = set(self.ohe_columns_) - set(X.columns)
62
+ for c in missing_cols:
63
+ X[c] = 0
64
+ X = X[self.ohe_columns_] # Enforce column order
65
+
66
+ if is_fitting:
67
+ self.na_imputed_cols_ = [col for col in X.columns if X[col].isnull().any()]
68
+
69
+ if self.na_imputed_cols_:
70
+ for col in self.na_imputed_cols_:
71
+ X[col + "_missing"] = X[col].isnull().astype(int)
72
+
73
+ if not is_fitting:
74
+ for col in self.median_values_:
75
+ if col in X.columns:
76
+ X[col] = X[col].fillna(self.median_values_[col])
77
+
78
+ return X
79
+
80
+ def _preprocess_X_fit(self, X, X_names, sample_weight):
81
+ if sample_weight.size > 0:
82
+ if sample_weight.ndim != 1:
83
+ raise ValueError("sample_weight must be a 1D array.")
84
+ if len(sample_weight) != X.shape[0]:
85
+ raise ValueError(
86
+ "sample_weight must have the same number of rows as X."
87
+ )
88
+ if np.any(np.isnan(sample_weight)) or np.any(np.isinf(sample_weight)):
89
+ raise ValueError("sample_weight cannot contain nan or infinite values.")
90
+ if np.any(sample_weight < 0):
91
+ raise ValueError("sample_weight cannot contain negative values.")
92
+ X = self._common_X_preprocessing(X, is_fitting=True, X_names=X_names)
93
+ self.median_values_ = {}
94
+ numeric_cols_for_median = [col for col in X.columns if "_missing" not in col]
95
+ for col in numeric_cols_for_median:
96
+ missing_mask = X[col].isnull()
97
+
98
+ if sample_weight.size > 0:
99
+ valid_indices = ~missing_mask
100
+ col_data = X.loc[valid_indices, col]
101
+ col_weights = sample_weight[valid_indices]
102
+ if col_data.empty:
103
+ median_val = 0
104
+ else:
105
+ col_data_np = col_data.to_numpy()
106
+ sort_indices = np.argsort(col_data_np, kind="stable")
107
+ sorted_data = col_data_np[sort_indices]
108
+ sorted_weights = col_weights[sort_indices]
109
+
110
+ cumulative_weights = np.cumsum(sorted_weights)
111
+ total_weight = cumulative_weights[-1]
112
+
113
+ median_weight_index = np.searchsorted(
114
+ cumulative_weights, total_weight / 2.0
115
+ )
116
+ if median_weight_index >= len(sorted_data):
117
+ median_weight_index = len(sorted_data) - 1
118
+ median_val = sorted_data[median_weight_index]
119
+ else:
120
+ median_val = X[col].median()
121
+
122
+ if pd.isna(median_val):
123
+ median_val = 0
124
+
125
+ self.median_values_[col] = median_val
126
+ X[col] = X[col].fillna(median_val)
127
+
128
+ self.final_training_columns_ = list(X.columns)
129
+ return X.values.astype(np.float64), list(X.columns)
130
+
131
+ def _preprocess_X_predict(self, X):
132
+ X = self._common_X_preprocessing(X, is_fitting=False)
133
+
134
+ if hasattr(self, "final_training_columns_"):
135
+ X = X[self.final_training_columns_]
136
+
137
+ return X.values.astype(np.float64)
138
+
139
+
140
+ class APLRRegressor(BaseAPLR):
13
141
  def __init__(
14
142
  self,
15
143
  m: int = 3000,
@@ -127,6 +255,13 @@ class APLRRegressor:
127
255
  self.mean_bias_correction = mean_bias_correction
128
256
  self.faster_convergence = faster_convergence
129
257
 
258
+ # Data transformations
259
+ self.median_values_ = {}
260
+ self.categorical_features_ = []
261
+ self.ohe_columns_ = []
262
+ self.na_imputed_cols_ = []
263
+ self.X_names_ = []
264
+
130
265
  # Creating aplr_cpp and setting parameters
131
266
  self.APLRRegressor = aplr_cpp.APLRRegressor()
132
267
  self.__set_params_cpp()
@@ -192,7 +327,7 @@ class APLRRegressor:
192
327
 
193
328
  def fit(
194
329
  self,
195
- X: FloatMatrix,
330
+ X: Union[pd.DataFrame, FloatMatrix],
196
331
  y: FloatVector,
197
332
  sample_weight: FloatVector = np.empty(0),
198
333
  X_names: List[str] = [],
@@ -207,12 +342,16 @@ class APLRRegressor:
207
342
  predictor_penalties_for_interactions: List[float] = [],
208
343
  predictor_min_observations_in_split: List[int] = [],
209
344
  ):
345
+ self._validate_X_fit_rows(X)
210
346
  self.__set_params_cpp()
347
+ X_transformed, X_names_transformed = self._preprocess_X_fit(
348
+ X, X_names, sample_weight
349
+ )
211
350
  self.APLRRegressor.fit(
212
- X,
351
+ X_transformed,
213
352
  y,
214
353
  sample_weight,
215
- X_names,
354
+ X_names_transformed,
216
355
  cv_observations,
217
356
  prioritized_predictors_indexes,
218
357
  monotonic_constraints,
@@ -226,42 +365,65 @@ class APLRRegressor:
226
365
  )
227
366
 
228
367
  def predict(
229
- self, X: FloatMatrix, cap_predictions_to_minmax_in_training: bool = True
368
+ self,
369
+ X: Union[pd.DataFrame, FloatMatrix],
370
+ cap_predictions_to_minmax_in_training: bool = True,
230
371
  ) -> FloatVector:
231
372
  if self.link_function == "custom_function":
232
373
  self.APLRRegressor.calculate_custom_transform_linear_predictor_to_predictions_function = (
233
374
  self.calculate_custom_transform_linear_predictor_to_predictions_function
234
375
  )
235
- return self.APLRRegressor.predict(X, cap_predictions_to_minmax_in_training)
376
+ X_transformed = self._preprocess_X_predict(X)
377
+ return self.APLRRegressor.predict(
378
+ X_transformed, cap_predictions_to_minmax_in_training
379
+ )
236
380
 
237
381
  def set_term_names(self, X_names: List[str]):
238
382
  self.APLRRegressor.set_term_names(X_names)
239
383
 
240
384
  def calculate_feature_importance(
241
- self, X: FloatMatrix, sample_weight: FloatVector = np.empty(0)
385
+ self,
386
+ X: Union[pd.DataFrame, FloatMatrix],
387
+ sample_weight: FloatVector = np.empty(0),
242
388
  ) -> FloatVector:
243
- return self.APLRRegressor.calculate_feature_importance(X, sample_weight)
389
+ X_transformed = self._preprocess_X_predict(X)
390
+ return self.APLRRegressor.calculate_feature_importance(
391
+ X_transformed, sample_weight
392
+ )
244
393
 
245
394
  def calculate_term_importance(
246
- self, X: FloatMatrix, sample_weight: FloatVector = np.empty(0)
395
+ self,
396
+ X: Union[pd.DataFrame, FloatMatrix],
397
+ sample_weight: FloatVector = np.empty(0),
247
398
  ) -> FloatVector:
248
- return self.APLRRegressor.calculate_term_importance(X, sample_weight)
399
+ X_transformed = self._preprocess_X_predict(X)
400
+ return self.APLRRegressor.calculate_term_importance(
401
+ X_transformed, sample_weight
402
+ )
249
403
 
250
- def calculate_local_feature_contribution(self, X: FloatMatrix) -> FloatMatrix:
251
- return self.APLRRegressor.calculate_local_feature_contribution(X)
404
+ def calculate_local_feature_contribution(
405
+ self, X: Union[pd.DataFrame, FloatMatrix]
406
+ ) -> FloatMatrix:
407
+ X_transformed = self._preprocess_X_predict(X)
408
+ return self.APLRRegressor.calculate_local_feature_contribution(X_transformed)
252
409
 
253
- def calculate_local_term_contribution(self, X: FloatMatrix) -> FloatMatrix:
254
- return self.APLRRegressor.calculate_local_term_contribution(X)
410
+ def calculate_local_term_contribution(
411
+ self, X: Union[pd.DataFrame, FloatMatrix]
412
+ ) -> FloatMatrix:
413
+ X_transformed = self._preprocess_X_predict(X)
414
+ return self.APLRRegressor.calculate_local_term_contribution(X_transformed)
255
415
 
256
416
  def calculate_local_contribution_from_selected_terms(
257
- self, X: FloatMatrix, predictor_indexes: List[int]
417
+ self, X: Union[pd.DataFrame, FloatMatrix], predictor_indexes: List[int]
258
418
  ) -> FloatVector:
419
+ X_transformed = self._preprocess_X_predict(X)
259
420
  return self.APLRRegressor.calculate_local_contribution_from_selected_terms(
260
- X, predictor_indexes
421
+ X_transformed, predictor_indexes
261
422
  )
262
423
 
263
- def calculate_terms(self, X: FloatMatrix) -> FloatMatrix:
264
- return self.APLRRegressor.calculate_terms(X)
424
+ def calculate_terms(self, X: Union[pd.DataFrame, FloatMatrix]) -> FloatMatrix:
425
+ X_transformed = self._preprocess_X_predict(X)
426
+ return self.APLRRegressor.calculate_terms(X_transformed)
265
427
 
266
428
  def get_term_names(self) -> List[str]:
267
429
  return self.APLRRegressor.get_term_names()
@@ -483,7 +645,7 @@ class APLRRegressor:
483
645
  return self
484
646
 
485
647
 
486
- class APLRClassifier:
648
+ class APLRClassifier(BaseAPLR):
487
649
  def __init__(
488
650
  self,
489
651
  m: int = 3000,
@@ -534,6 +696,13 @@ class APLRClassifier:
534
696
  self.max_terms = max_terms
535
697
  self.ridge_penalty = ridge_penalty
536
698
 
699
+ # Data transformations
700
+ self.median_values_ = {}
701
+ self.categorical_features_ = []
702
+ self.ohe_columns_ = []
703
+ self.na_imputed_cols_ = []
704
+ self.X_names_ = []
705
+
537
706
  # Creating aplr_cpp and setting parameters
538
707
  self.APLRClassifier = aplr_cpp.APLRClassifier()
539
708
  self.__set_params_cpp()
@@ -571,8 +740,8 @@ class APLRClassifier:
571
740
 
572
741
  def fit(
573
742
  self,
574
- X: FloatMatrix,
575
- y: List[str],
743
+ X: Union[pd.DataFrame, FloatMatrix],
744
+ y: Union[FloatVector, List[str]],
576
745
  sample_weight: FloatVector = np.empty(0),
577
746
  X_names: List[str] = [],
578
747
  cv_observations: IntMatrix = np.empty([0, 0]),
@@ -584,12 +753,22 @@ class APLRClassifier:
584
753
  predictor_penalties_for_interactions: List[float] = [],
585
754
  predictor_min_observations_in_split: List[int] = [],
586
755
  ):
756
+ self._validate_X_fit_rows(X)
587
757
  self.__set_params_cpp()
758
+ X_transformed, X_names_transformed = self._preprocess_X_fit(
759
+ X, X_names, sample_weight
760
+ )
761
+
762
+ if isinstance(y, np.ndarray):
763
+ y = y.astype(str).tolist()
764
+ elif isinstance(y, list) and y and not isinstance(y[0], str):
765
+ y = [str(val) for val in y]
766
+
588
767
  self.APLRClassifier.fit(
589
- X,
768
+ X_transformed,
590
769
  y,
591
770
  sample_weight,
592
- X_names,
771
+ X_names_transformed,
593
772
  cv_observations,
594
773
  prioritized_predictors_indexes,
595
774
  monotonic_constraints,
@@ -603,19 +782,30 @@ class APLRClassifier:
603
782
  self.classes_ = np.arange(len(self.APLRClassifier.get_categories()))
604
783
 
605
784
  def predict_class_probabilities(
606
- self, X: FloatMatrix, cap_predictions_to_minmax_in_training: bool = False
785
+ self,
786
+ X: Union[pd.DataFrame, FloatMatrix],
787
+ cap_predictions_to_minmax_in_training: bool = False,
607
788
  ) -> FloatMatrix:
789
+ X_transformed = self._preprocess_X_predict(X)
608
790
  return self.APLRClassifier.predict_class_probabilities(
609
- X, cap_predictions_to_minmax_in_training
791
+ X_transformed, cap_predictions_to_minmax_in_training
610
792
  )
611
793
 
612
794
  def predict(
613
- self, X: FloatMatrix, cap_predictions_to_minmax_in_training: bool = False
795
+ self,
796
+ X: Union[pd.DataFrame, FloatMatrix],
797
+ cap_predictions_to_minmax_in_training: bool = False,
614
798
  ) -> List[str]:
615
- return self.APLRClassifier.predict(X, cap_predictions_to_minmax_in_training)
799
+ X_transformed = self._preprocess_X_predict(X)
800
+ return self.APLRClassifier.predict(
801
+ X_transformed, cap_predictions_to_minmax_in_training
802
+ )
616
803
 
617
- def calculate_local_feature_contribution(self, X: FloatMatrix) -> FloatMatrix:
618
- return self.APLRClassifier.calculate_local_feature_contribution(X)
804
+ def calculate_local_feature_contribution(
805
+ self, X: Union[pd.DataFrame, FloatMatrix]
806
+ ) -> FloatMatrix:
807
+ X_transformed = self._preprocess_X_predict(X)
808
+ return self.APLRClassifier.calculate_local_feature_contribution(X_transformed)
619
809
 
620
810
  def get_categories(self) -> List[str]:
621
811
  return self.APLRClassifier.get_categories()
@@ -724,7 +914,7 @@ class APLRTuner:
724
914
  grid = [dict(zip(keys, combination)) for combination in combinations]
725
915
  return grid
726
916
 
727
- def fit(self, X: FloatMatrix, y: FloatVector, **kwargs):
917
+ def fit(self, X: Union[pd.DataFrame, FloatMatrix], y: FloatVector, **kwargs):
728
918
  self.cv_results: List[Dict[str, float]] = []
729
919
  best_validation_result = np.inf
730
920
  for params in self.parameter_grid:
@@ -742,10 +932,14 @@ class APLRTuner:
742
932
  self.best_model = model
743
933
  self.cv_results = sorted(self.cv_results, key=lambda x: x["cv_error"])
744
934
 
745
- def predict(self, X: FloatMatrix, **kwargs) -> Union[FloatVector, List[str]]:
935
+ def predict(
936
+ self, X: Union[pd.DataFrame, FloatMatrix], **kwargs
937
+ ) -> Union[FloatVector, List[str]]:
746
938
  return self.best_model.predict(X, **kwargs)
747
939
 
748
- def predict_class_probabilities(self, X: FloatMatrix, **kwargs) -> FloatMatrix:
940
+ def predict_class_probabilities(
941
+ self, X: Union[pd.DataFrame, FloatMatrix], **kwargs
942
+ ) -> FloatMatrix:
749
943
  if self.is_regressor == False:
750
944
  return self.best_model.predict_class_probabilities(X, **kwargs)
751
945
  else:
@@ -753,7 +947,9 @@ class APLRTuner:
753
947
  "predict_class_probabilities is only possible when is_regressor is False"
754
948
  )
755
949
 
756
- def predict_proba(self, X: FloatMatrix, **kwargs) -> FloatMatrix:
950
+ def predict_proba(
951
+ self, X: Union[pd.DataFrame, FloatMatrix], **kwargs
952
+ ) -> FloatMatrix:
757
953
  return self.predict_class_probabilities(X, **kwargs)
758
954
 
759
955
  def get_best_estimator(self) -> Union[APLRClassifier, APLRRegressor]:
@@ -0,0 +1,34 @@
1
+ Metadata-Version: 2.4
2
+ Name: aplr
3
+ Version: 10.18.0
4
+ Summary: Automatic Piecewise Linear Regression
5
+ Home-page: https://github.com/ottenbreit-data-science/aplr
6
+ Author: Mathias von Ottenbreit
7
+ Author-email: ottenbreitdatascience@gmail.com
8
+ License: MIT
9
+ Platform: Windows
10
+ Platform: Linux
11
+ Platform: MacOS
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: numpy>=1.11
17
+ Requires-Dist: pandas>=1.0.0
18
+ Provides-Extra: plots
19
+ Requires-Dist: matplotlib>=3.0; extra == "plots"
20
+ Dynamic: author
21
+ Dynamic: author-email
22
+ Dynamic: classifier
23
+ Dynamic: description
24
+ Dynamic: description-content-type
25
+ Dynamic: home-page
26
+ Dynamic: license
27
+ Dynamic: license-file
28
+ Dynamic: platform
29
+ Dynamic: provides-extra
30
+ Dynamic: requires-dist
31
+ Dynamic: requires-python
32
+ Dynamic: summary
33
+
34
+ The documentation for Automatic Piecewise Linear Regression is available at [https://github.com/ottenbreit-data-science/aplr](https://github.com/ottenbreit-data-science/aplr).
@@ -0,0 +1,8 @@
1
+ aplr_cpp.cp312-win_amd64.pyd,sha256=6zNh0yut-s9QgpyPomUssWYxQwbop4vuIHOHRAdv1pk,664576
2
+ aplr/__init__.py,sha256=oDFSgVytP_qQ8ilun6oHxKr-DYEeqjEQp5FciX45lls,21
3
+ aplr/aplr.py,sha256=cEI63m6-5U1VVou-CfKIQ85ys0DL8rqi9ghWFBK3BxY,41090
4
+ aplr-10.18.0.dist-info/licenses/LICENSE,sha256=YOMo-RaL4P7edMZGD96-NskKpxyMZdP3-WiiMMmihNk,1134
5
+ aplr-10.18.0.dist-info/METADATA,sha256=Wsrqg1LzAYOKTuAcjW8s3YXmJUsjvHx1k2AWTlNntiY,1048
6
+ aplr-10.18.0.dist-info/WHEEL,sha256=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
7
+ aplr-10.18.0.dist-info/top_level.txt,sha256=DXVC0RIFGpzVnPeKWAZTXQdJheOEZL51Wip6Fx7zbR4,14
8
+ aplr-10.18.0.dist-info/RECORD,,
Binary file
@@ -1,68 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: aplr
3
- Version: 10.17.0
4
- Summary: Automatic Piecewise Linear Regression
5
- Home-page: https://github.com/ottenbreit-data-science/aplr
6
- Author: Mathias von Ottenbreit
7
- Author-email: ottenbreitdatascience@gmail.com
8
- License: MIT
9
- Platform: Windows
10
- Platform: Linux
11
- Platform: MacOS
12
- Classifier: License :: OSI Approved :: MIT License
13
- Requires-Python: >=3.8
14
- Description-Content-Type: text/markdown
15
- License-File: LICENSE
16
- Requires-Dist: numpy>=1.11
17
- Provides-Extra: plots
18
- Requires-Dist: matplotlib>=3.0; extra == "plots"
19
- Dynamic: author
20
- Dynamic: author-email
21
- Dynamic: classifier
22
- Dynamic: description
23
- Dynamic: description-content-type
24
- Dynamic: home-page
25
- Dynamic: license
26
- Dynamic: license-file
27
- Dynamic: platform
28
- Dynamic: provides-extra
29
- Dynamic: requires-dist
30
- Dynamic: requires-python
31
- Dynamic: summary
32
-
33
- # APLR
34
- **Automatic Piecewise Linear Regression**
35
-
36
- ## About
37
- APLR allows you to build predictive and interpretable regression or classification machine learning models in Python, using the Automatic Piecewise Linear Regression (APLR) methodology developed by Mathias von Ottenbreit. APLR often rivals tree-based methods in predictive accuracy, while offering smoother, more interpretable predictions.
38
-
39
- For further details, see the [documentation](https://github.com/ottenbreit-data-science/aplr/tree/main/documentation). You may also read the published article for additional insights: [Link 1](https://link.springer.com/article/10.1007/s00180-024-01475-4) and [Link 2](https://rdcu.be/dz7bF). Additional functionality has been added since the article was published.
40
-
41
- ## Installation
42
- To install APLR, use the following command:
43
-
44
- ```bash
45
- pip install aplr
46
- ```
47
-
48
- To include dependencies for plotting, use this command instead:
49
-
50
- ```bash
51
- pip install aplr[plots]
52
- ```
53
-
54
- ## Availability
55
- APLR is available for Windows, most Linux distributions, and macOS.
56
-
57
- ## Usage
58
- Example Python scripts are available [here](https://github.com/ottenbreit-data-science/aplr/tree/main/examples).
59
-
60
- ## Sponsorship
61
- Consider sponsoring Von Ottenbreit Data Science by clicking the **Sponsor** button on the repository. Sufficient funding will help maintain and further develop APLR.
62
-
63
- ## API Reference
64
- - [API reference for regression](https://github.com/ottenbreit-data-science/aplr/blob/main/API_REFERENCE_FOR_REGRESSION.md)
65
- - [API reference for classification](https://github.com/ottenbreit-data-science/aplr/blob/main/API_REFERENCE_FOR_CLASSIFICATION.md)
66
-
67
- ## Contact Information
68
- For inquiries, please email: [ottenbreitdatascience@gmail.com](mailto:ottenbreitdatascience@gmail.com)
@@ -1,8 +0,0 @@
1
- aplr_cpp.cp312-win_amd64.pyd,sha256=p3JsaFlbPWuLMf9fsxX0v0rKymeIxK8_N1ze58HGxTI,664064
2
- aplr/__init__.py,sha256=oDFSgVytP_qQ8ilun6oHxKr-DYEeqjEQp5FciX45lls,21
3
- aplr/aplr.py,sha256=obmpNNppOrnzQxhYzShYzNe02lQv17-YRWUklf8IXds,33302
4
- aplr-10.17.0.dist-info/licenses/LICENSE,sha256=YOMo-RaL4P7edMZGD96-NskKpxyMZdP3-WiiMMmihNk,1134
5
- aplr-10.17.0.dist-info/METADATA,sha256=0-e_AeSLFoIr-ofT8xyweTMgwLbOvkdmNRGwOtgAo3g,2627
6
- aplr-10.17.0.dist-info/WHEEL,sha256=8UP9x9puWI0P1V_d7K2oMTBqfeLNm21CTzZ_Ptr0NXU,101
7
- aplr-10.17.0.dist-info/top_level.txt,sha256=DXVC0RIFGpzVnPeKWAZTXQdJheOEZL51Wip6Fx7zbR4,14
8
- aplr-10.17.0.dist-info/RECORD,,
File without changes