dfm-python 0.2.7__py3-none-any.whl → 0.2.8__py3-none-any.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.
dfm_python/__init__.py CHANGED
@@ -50,7 +50,7 @@ Example (Low-level API - For advanced usage):
50
50
  For detailed documentation, see the README.md file and the tutorial notebooks/scripts.
51
51
  """
52
52
 
53
- __version__ = "0.2.7"
53
+ __version__ = "0.2.8"
54
54
 
55
55
  from .config import DFMConfig, SeriesConfig, BlockConfig, Params, DEFAULT_GLOBAL_BLOCK_NAME
56
56
  from .config import (
@@ -4,12 +4,10 @@ from typing import Optional, Dict, Any, Tuple, TYPE_CHECKING
4
4
  import numpy as np
5
5
  import logging
6
6
 
7
- try:
8
- import pandas as pd
9
- PANDAS_AVAILABLE = True
10
- except ImportError:
11
- PANDAS_AVAILABLE = False
12
- pd = None
7
+ import pandas as pd
8
+ from scipy.linalg import orthogonal_procrustes
9
+
10
+ PANDAS_AVAILABLE = True
13
11
 
14
12
  from ..config import DFMConfig
15
13
 
@@ -96,6 +94,217 @@ def calculate_rmse(actual: np.ndarray, predicted: np.ndarray,
96
94
  return rmse_overall, rmse_per_series
97
95
 
98
96
 
97
+ def evaluate_factor_estimation(
98
+ true_factors: np.ndarray,
99
+ estimated_factors: np.ndarray,
100
+ use_procrustes: bool = True
101
+ ) -> Dict[str, Any]:
102
+ """Evaluate how well factors are estimated (synthetic-data diagnostic).
103
+
104
+ This function compares known ``true_factors`` with ``estimated_factors``
105
+ (typically ``result.Z[:, :num_factors]``) and returns:
106
+
107
+ - Per-factor correlations (before any rotation)
108
+ - Optionally Procrustes-rotated factors and an overall correlation
109
+
110
+ Parameters
111
+ ----------
112
+ true_factors : np.ndarray
113
+ Array of shape (T, k) with generated factors used to build the data.
114
+ estimated_factors : np.ndarray
115
+ Array of shape (T, k_est) with estimated factors. Only the first
116
+ ``k = min(k, k_est)`` columns are compared.
117
+ use_procrustes : bool, default True
118
+ If True and SciPy is available, apply orthogonal Procrustes rotation
119
+ to align estimated factors with true factors before computing the
120
+ overall correlation.
121
+
122
+ Returns
123
+ -------
124
+ Dict[str, Any]
125
+ {
126
+ 'num_factors': int,
127
+ 'correlation_per_factor': np.ndarray (k,),
128
+ 'overall_correlation': float or None,
129
+ 'rotation_matrix': np.ndarray (k, k) or None,
130
+ 'aligned_factors': np.ndarray (T, k) or None,
131
+ }
132
+
133
+ Notes
134
+ -----
135
+ - This is primarily intended for synthetic-data experiments where the
136
+ true latent factors are known.
137
+ - Correlations are computed factor-by-factor, ignoring any time points
138
+ with NaN values.
139
+ - Procrustes rotation is useful because factors are only identified up
140
+ to sign, scale (if not constrained), and rotation in general.
141
+ """
142
+ if true_factors.ndim != 2 or estimated_factors.ndim != 2:
143
+ raise ValueError(
144
+ f"true_factors and estimated_factors must be 2D, "
145
+ f"got shapes {true_factors.shape} and {estimated_factors.shape}"
146
+ )
147
+
148
+ T_true, k_true = true_factors.shape
149
+ T_est, k_est = estimated_factors.shape
150
+ if T_true != T_est:
151
+ raise ValueError(
152
+ f"Time dimension must match, got {T_true} and {T_est}"
153
+ )
154
+
155
+ k = min(k_true, k_est)
156
+ if k == 0:
157
+ raise ValueError("At least one factor is required for comparison.")
158
+
159
+ F_true = np.asarray(true_factors[:, :k], dtype=float)
160
+ F_est = np.asarray(estimated_factors[:, :k], dtype=float)
161
+
162
+ # Per-factor correlations (no rotation)
163
+ corr_per_factor = np.full(k, np.nan, dtype=float)
164
+ for j in range(k):
165
+ mask_j = np.isfinite(F_true[:, j]) & np.isfinite(F_est[:, j])
166
+ if np.sum(mask_j) > 1:
167
+ corr = np.corrcoef(F_true[mask_j, j], F_est[mask_j, j])[0, 1]
168
+ corr_per_factor[j] = float(corr)
169
+
170
+ rotation_matrix = None
171
+ aligned_factors = None
172
+ overall_corr = None
173
+
174
+ if use_procrustes:
175
+ # Center factors before Procrustes to focus on shape, not level.
176
+ F_true_centered = F_true - np.nanmean(F_true, axis=0, keepdims=True)
177
+ F_est_centered = F_est - np.nanmean(F_est, axis=0, keepdims=True)
178
+
179
+ # Replace any remaining NaNs with 0 before Procrustes (rare in synthetic tests)
180
+ F_true_clean = np.where(np.isfinite(F_true_centered), F_true_centered, 0.0)
181
+ F_est_clean = np.where(np.isfinite(F_est_centered), F_est_centered, 0.0)
182
+
183
+ try:
184
+ # Find R such that F_est_clean @ R ≈ F_true_clean
185
+ R, _ = orthogonal_procrustes(F_est_clean, F_true_clean) # type: ignore[arg-type]
186
+ rotation_matrix = R
187
+ aligned_factors = F_est @ R
188
+
189
+ mask_all = np.isfinite(F_true) & np.isfinite(aligned_factors)
190
+ if np.any(mask_all):
191
+ overall_corr = float(
192
+ np.corrcoef(
193
+ F_true[mask_all].ravel(),
194
+ aligned_factors[mask_all].ravel()
195
+ )[0, 1]
196
+ )
197
+ except Exception as e:
198
+ _logger.debug(f"Procrustes rotation failed in evaluate_factor_estimation: {e}")
199
+
200
+ return {
201
+ "num_factors": k,
202
+ "correlation_per_factor": corr_per_factor,
203
+ "overall_correlation": overall_corr,
204
+ "rotation_matrix": rotation_matrix,
205
+ "aligned_factors": aligned_factors,
206
+ }
207
+
208
+
209
+ def evaluate_loading_estimation(
210
+ true_loadings: np.ndarray,
211
+ estimated_loadings: np.ndarray,
212
+ rotation_matrix: Optional[np.ndarray] = None
213
+ ) -> Dict[str, Any]:
214
+ """Evaluate loading matrix estimation accuracy (synthetic-data diagnostic).
215
+
216
+ Parameters
217
+ ----------
218
+ true_loadings : np.ndarray
219
+ Array of shape (N, k_true) with the loadings used to generate data.
220
+ estimated_loadings : np.ndarray
221
+ Array of shape (N, k_est) with estimated loadings from the model
222
+ (typically ``result.C[:, :num_factors]``).
223
+ rotation_matrix : np.ndarray, optional
224
+ Orthogonal rotation matrix of shape (k_est, k_rot) returned by
225
+ :func:`evaluate_factor_estimation`. If provided, the estimated
226
+ loadings are rotated as ``estimated_loadings @ rotation_matrix``
227
+ before comparison.
228
+
229
+ Returns
230
+ -------
231
+ Dict[str, Any]
232
+ {
233
+ 'num_factors': int,
234
+ 'correlation_per_factor': np.ndarray (k,),
235
+ 'rmse_per_series': np.ndarray (N,),
236
+ 'overall_rmse': float,
237
+ 'aligned_loadings': np.ndarray (N, k),
238
+ }
239
+
240
+ Notes
241
+ -----
242
+ - RMSE is computed per series (row-wise) across factors.
243
+ - This function assumes synthetic data where ``true_loadings`` are known.
244
+ """
245
+ if true_loadings.ndim != 2 or estimated_loadings.ndim != 2:
246
+ raise ValueError(
247
+ f"true_loadings and estimated_loadings must be 2D, "
248
+ f"got shapes {true_loadings.shape} and {estimated_loadings.shape}"
249
+ )
250
+
251
+ N_true, k_true = true_loadings.shape
252
+ N_est, k_est = estimated_loadings.shape
253
+ if N_true != N_est:
254
+ raise ValueError(
255
+ f"Number of series (rows) must match, got {N_true} and {N_est}"
256
+ )
257
+
258
+ # Apply rotation if provided (align loadings with rotated factors)
259
+ L_est = np.asarray(estimated_loadings, dtype=float)
260
+ if rotation_matrix is not None:
261
+ if rotation_matrix.ndim != 2 or rotation_matrix.shape[0] != k_est:
262
+ raise ValueError(
263
+ f"rotation_matrix shape {rotation_matrix.shape} incompatible "
264
+ f"with estimated_loadings shape {estimated_loadings.shape}"
265
+ )
266
+ L_est = L_est @ rotation_matrix
267
+
268
+ k = min(k_true, L_est.shape[1])
269
+ if k == 0:
270
+ raise ValueError("At least one factor/loading column is required.")
271
+
272
+ L_true = np.asarray(true_loadings[:, :k], dtype=float)
273
+ L_est_k = np.asarray(L_est[:, :k], dtype=float)
274
+
275
+ # Per-factor loading correlations
276
+ corr_per_factor = np.full(k, np.nan, dtype=float)
277
+ for j in range(k):
278
+ mask_j = np.isfinite(L_true[:, j]) & np.isfinite(L_est_k[:, j])
279
+ if np.sum(mask_j) > 1:
280
+ corr = np.corrcoef(L_true[mask_j, j], L_est_k[mask_j, j])[0, 1]
281
+ corr_per_factor[j] = float(corr)
282
+
283
+ # RMSE per series (row-wise across factors)
284
+ diff = L_true - L_est_k
285
+ mask = np.isfinite(L_true) & np.isfinite(L_est_k)
286
+ rmse_per_series = np.full(N_true, np.nan, dtype=float)
287
+ for i in range(N_true):
288
+ mask_i = mask[i, :]
289
+ if np.any(mask_i):
290
+ rmse_per_series[i] = float(
291
+ np.sqrt(np.mean(diff[i, mask_i] ** 2))
292
+ )
293
+
294
+ if np.any(mask):
295
+ overall_rmse = float(np.sqrt(np.mean(diff[mask] ** 2)))
296
+ else:
297
+ overall_rmse = float("nan")
298
+
299
+ return {
300
+ "num_factors": k,
301
+ "correlation_per_factor": corr_per_factor,
302
+ "rmse_per_series": rmse_per_series,
303
+ "overall_rmse": overall_rmse,
304
+ "aligned_loadings": L_est_k,
305
+ }
306
+
307
+
99
308
  def _display_dfm_tables(Res: DFMResult, config: DFMConfig, nQ: int) -> None:
100
309
  """Display DFM estimation output tables.
101
310
 
dfm_python/core/em.py CHANGED
@@ -949,55 +949,55 @@ def em_step(params: EMStepParams) -> Tuple[np.ndarray, np.ndarray, np.ndarray, n
949
949
  # Keep C fixed for idio components (not updated in M-step)
950
950
  else:
951
951
  # Fallback to old approach for backward compatibility
952
- i_subset = slice(idio_start_idx, idio_start_idx + n_idio)
953
- i_subset_slice = slice(i_subset.start, i_subset.stop)
954
- Z_idio = Zsmooth[i_subset_slice, 1:] if idio_start_idx < Zsmooth.shape[1] else np.zeros((n_idio, T))
955
- n_idio_actual = Z_idio.shape[0] if Z_idio.shape[0] > 0 else n_idio
956
- expected_idio_current_sq = np.sum(Z_idio**2, axis=1)
957
- vsmooth_idio_block = vsmooth[i_subset_slice, :, :][:, i_subset_slice, 1:]
958
- vsmooth_idio_sum = np.sum(vsmooth_idio_block, axis=2)
959
- if vsmooth_idio_sum.ndim == 2 and vsmooth_idio_sum.shape[0] >= n_idio_actual and vsmooth_idio_sum.shape[1] >= n_idio_actual:
960
- vsmooth_idio_diag = np.diag(vsmooth_idio_sum[:n_idio_actual, :n_idio_actual])
961
- else:
962
- vsmooth_idio_diag = np.zeros(n_idio_actual)
963
- expected_idio_current_sq = expected_idio_current_sq + vsmooth_idio_diag
964
- Z_idio_lag = Zsmooth[i_subset_slice, :-1]
965
- expected_idio_lag_sq = np.sum(Z_idio_lag**2, axis=1)
966
- vsmooth_lag_block = vsmooth[i_subset_slice, :, :][:, i_subset_slice, :-1]
967
- vsmooth_lag_sum = np.sum(vsmooth_lag_block, axis=2)
968
- if vsmooth_lag_sum.ndim == 2 and vsmooth_lag_sum.shape[0] >= n_idio_actual and vsmooth_lag_sum.shape[1] >= n_idio_actual:
969
- vsmooth_lag_diag = np.diag(vsmooth_lag_sum[:n_idio_actual, :n_idio_actual])
970
- else:
971
- vsmooth_lag_diag = np.zeros(n_idio_actual)
972
- expected_idio_lag_sq = expected_idio_lag_sq + vsmooth_lag_diag
973
- min_cols = min(Z_idio.shape[1], Z_idio_lag.shape[1])
974
- expected_idio_cross = np.sum(Z_idio[:, :min_cols] * Z_idio_lag[:, :min_cols], axis=1)
975
- vvsmooth_block = vvsmooth[i_subset_slice, :, :][:, i_subset_slice, :]
976
- vvsmooth_sum = np.sum(vvsmooth_block, axis=2)
977
- if vvsmooth_sum.ndim == 2 and vvsmooth_sum.shape[0] >= n_idio_actual and vvsmooth_sum.shape[1] >= n_idio_actual:
978
- vvsmooth_diag = np.diag(vvsmooth_sum[:n_idio_actual, :n_idio_actual])
979
- else:
980
- vvsmooth_diag = np.zeros(n_idio_actual)
981
- expected_idio_cross = expected_idio_cross + vvsmooth_diag
982
- ar_coeffs_diag, _ = _estimate_ar_coefficient(expected_idio_cross, expected_idio_lag_sq, vsmooth_sum=vsmooth_lag_diag)
983
- A_block_idio = np.diag(ar_coeffs_diag)
984
- innovation_cov_diag = (np.maximum(expected_idio_current_sq, 0.0) - ar_coeffs_diag * expected_idio_cross) / T
985
- innovation_cov_diag = np.maximum(innovation_cov_diag, MIN_INNOVATION_VARIANCE)
986
- Q_block_idio = np.diag(innovation_cov_diag)
987
- i_subset_size = i_subset.stop - i_subset.start
988
- if n_idio_actual == i_subset_size:
989
- A_new[i_subset, i_subset] = A_block_idio
990
- Q_new[i_subset, i_subset] = Q_block_idio
991
- elif n_idio_actual < i_subset_size:
992
- A_new[i_subset.start:i_subset.start + n_idio_actual, i_subset.start:i_subset.start + n_idio_actual] = A_block_idio
993
- Q_new[i_subset.start:i_subset.start + n_idio_actual, i_subset.start:i_subset.start + n_idio_actual] = Q_block_idio
994
- else:
995
- A_new[i_subset, i_subset] = A_block_idio[:i_subset_size, :i_subset_size]
996
- Q_new[i_subset, i_subset] = Q_block_idio[:i_subset_size, :i_subset_size]
997
- vsmooth_sub = vsmooth[i_subset_slice, :, :][:, i_subset_slice, 0]
998
- vsmooth_diag = np.diag(vsmooth_sub[:n_idio_actual, :n_idio_actual]) if vsmooth_sub.ndim == 2 else np.zeros(n_idio_actual)
999
- for idx in range(min(n_idio_actual, i_subset_size)):
1000
- V_0_new[i_subset.start + idx, i_subset.start + idx] = vsmooth_diag[idx] if idx < len(vsmooth_diag) else 0.0
952
+ i_subset = slice(idio_start_idx, idio_start_idx + n_idio)
953
+ i_subset_slice = slice(i_subset.start, i_subset.stop)
954
+ Z_idio = Zsmooth[i_subset_slice, 1:] if idio_start_idx < Zsmooth.shape[1] else np.zeros((n_idio, T))
955
+ n_idio_actual = Z_idio.shape[0] if Z_idio.shape[0] > 0 else n_idio
956
+ expected_idio_current_sq = np.sum(Z_idio**2, axis=1)
957
+ vsmooth_idio_block = vsmooth[i_subset_slice, :, :][:, i_subset_slice, 1:]
958
+ vsmooth_idio_sum = np.sum(vsmooth_idio_block, axis=2)
959
+ if vsmooth_idio_sum.ndim == 2 and vsmooth_idio_sum.shape[0] >= n_idio_actual and vsmooth_idio_sum.shape[1] >= n_idio_actual:
960
+ vsmooth_idio_diag = np.diag(vsmooth_idio_sum[:n_idio_actual, :n_idio_actual])
961
+ else:
962
+ vsmooth_idio_diag = np.zeros(n_idio_actual)
963
+ expected_idio_current_sq = expected_idio_current_sq + vsmooth_idio_diag
964
+ Z_idio_lag = Zsmooth[i_subset_slice, :-1]
965
+ expected_idio_lag_sq = np.sum(Z_idio_lag**2, axis=1)
966
+ vsmooth_lag_block = vsmooth[i_subset_slice, :, :][:, i_subset_slice, :-1]
967
+ vsmooth_lag_sum = np.sum(vsmooth_lag_block, axis=2)
968
+ if vsmooth_lag_sum.ndim == 2 and vsmooth_lag_sum.shape[0] >= n_idio_actual and vsmooth_lag_sum.shape[1] >= n_idio_actual:
969
+ vsmooth_lag_diag = np.diag(vsmooth_lag_sum[:n_idio_actual, :n_idio_actual])
970
+ else:
971
+ vsmooth_lag_diag = np.zeros(n_idio_actual)
972
+ expected_idio_lag_sq = expected_idio_lag_sq + vsmooth_lag_diag
973
+ min_cols = min(Z_idio.shape[1], Z_idio_lag.shape[1])
974
+ expected_idio_cross = np.sum(Z_idio[:, :min_cols] * Z_idio_lag[:, :min_cols], axis=1)
975
+ vvsmooth_block = vvsmooth[i_subset_slice, :, :][:, i_subset_slice, :]
976
+ vvsmooth_sum = np.sum(vvsmooth_block, axis=2)
977
+ if vvsmooth_sum.ndim == 2 and vvsmooth_sum.shape[0] >= n_idio_actual and vvsmooth_sum.shape[1] >= n_idio_actual:
978
+ vvsmooth_diag = np.diag(vvsmooth_sum[:n_idio_actual, :n_idio_actual])
979
+ else:
980
+ vvsmooth_diag = np.zeros(n_idio_actual)
981
+ expected_idio_cross = expected_idio_cross + vvsmooth_diag
982
+ ar_coeffs_diag, _ = _estimate_ar_coefficient(expected_idio_cross, expected_idio_lag_sq, vsmooth_sum=vsmooth_lag_diag)
983
+ A_block_idio = np.diag(ar_coeffs_diag)
984
+ innovation_cov_diag = (np.maximum(expected_idio_current_sq, 0.0) - ar_coeffs_diag * expected_idio_cross) / T
985
+ innovation_cov_diag = np.maximum(innovation_cov_diag, MIN_INNOVATION_VARIANCE)
986
+ Q_block_idio = np.diag(innovation_cov_diag)
987
+ i_subset_size = i_subset.stop - i_subset.start
988
+ if n_idio_actual == i_subset_size:
989
+ A_new[i_subset, i_subset] = A_block_idio
990
+ Q_new[i_subset, i_subset] = Q_block_idio
991
+ elif n_idio_actual < i_subset_size:
992
+ A_new[i_subset.start:i_subset.start + n_idio_actual, i_subset.start:i_subset.start + n_idio_actual] = A_block_idio
993
+ Q_new[i_subset.start:i_subset.start + n_idio_actual, i_subset.start:i_subset.start + n_idio_actual] = Q_block_idio
994
+ else:
995
+ A_new[i_subset, i_subset] = A_block_idio[:i_subset_size, :i_subset_size]
996
+ Q_new[i_subset, i_subset] = Q_block_idio[:i_subset_size, :i_subset_size]
997
+ vsmooth_sub = vsmooth[i_subset_slice, :, :][:, i_subset_slice, 0]
998
+ vsmooth_diag = np.diag(vsmooth_sub[:n_idio_actual, :n_idio_actual]) if vsmooth_sub.ndim == 2 else np.zeros(n_idio_actual)
999
+ for idx in range(min(n_idio_actual, i_subset_size)):
1000
+ V_0_new[i_subset.start + idx, i_subset.start + idx] = vsmooth_diag[idx] if idx < len(vsmooth_diag) else 0.0
1001
1001
 
1002
1002
  Z_0 = Zsmooth[0, :].copy()
1003
1003
  nanY = np.isnan(y)
@@ -605,14 +605,14 @@ def _safe_determinant(M: np.ndarray, use_logdet: bool = True) -> float:
605
605
  if not np.isfinite(log_det) or sign <= 0:
606
606
  return 0.0
607
607
  # Avoid overflow in exp
608
- if log_det > 700:
609
- _logger.debug("_safe_determinant: log_det too large, returning 0.0")
610
- return 0.0
611
- with warnings.catch_warnings():
612
- warnings.filterwarnings('ignore', category=RuntimeWarning)
608
+ if log_det > 700:
609
+ _logger.debug("_safe_determinant: log_det too large, returning 0.0")
610
+ return 0.0
611
+ with warnings.catch_warnings():
612
+ warnings.filterwarnings('ignore', category=RuntimeWarning)
613
613
  det = np.exp(log_det)
614
- if np.isfinite(det):
615
- return float(det)
614
+ if np.isfinite(det):
615
+ return float(det)
616
616
  except Exception:
617
617
  pass
618
618
  except (np.linalg.LinAlgError, ValueError, OverflowError, RuntimeWarning):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dfm-python
3
- Version: 0.2.7
3
+ Version: 0.2.8
4
4
  Summary: Dynamic Factor Model (DFM) estimation and nowcasting in Python
5
5
  Project-URL: Homepage, https://github.com/yourusername/dfm-python
6
6
  Project-URL: Documentation, https://github.com/yourusername/dfm-python#readme
@@ -1,4 +1,4 @@
1
- dfm_python/__init__.py,sha256=YYhRgV773DAFbaTrfF6LVF45rmbR8SpsH6LOYtDAHnU,5589
1
+ dfm_python/__init__.py,sha256=oz0p9JreUg6D9SfU2vUAFMRwf7JwHaa3dQDy87YHtIw,5589
2
2
  dfm_python/api.py,sha256=-mmV7aaXOAqVW9HPhTEut7T6wN1EyW0rS3J0y-z-MRs,16221
3
3
  dfm_python/config.py,sha256=bBiKfoETohNVJ3brRbY1330C1nQROybCR3co9iagIKs,46978
4
4
  dfm_python/config_sources.py,sha256=6LTK7k5WJpnayMmtxtYwZ9SQi_hGXoz-Vg4sRGTabI0,21798
@@ -8,11 +8,11 @@ dfm_python/kalman.py,sha256=EXhloLbpzy7GKG9KFrSZt9pRp0y3CZyvycPVbvUWxq8,18074
8
8
  dfm_python/news.py,sha256=iXbJnsvH5FgKToPzKnqIpTosrZu-QHXBNHjsqwMYrHo,32583
9
9
  dfm_python/utils.py,sha256=qOAJm2KG71yP2a29iJVDGOC-ZUS_HPL5oAo16EgNXA4,15709
10
10
  dfm_python/core/__init__.py,sha256=oDrhAoc1W_ODohgufQ-gN0uri8-vZI0EXsF0dysth_A,331
11
- dfm_python/core/diagnostics.py,sha256=2Cm8XIN5jE9jL3FssEUmZNQy12FPuKvYs1k3bYKAySw,20224
12
- dfm_python/core/em.py,sha256=ltMVW1izAVmMwwYKf3GIFLAP-nLAgGy9zdOenuK-SiU,72509
11
+ dfm_python/core/diagnostics.py,sha256=1ninWcQlWhJmfUZq9kky7IQoRf_6fI_IlVzzSb7apRQ,28085
12
+ dfm_python/core/em.py,sha256=jbtsFHynMDtblZTtlzgodX04e86BBB9hPn20MMnV3w0,72705
13
13
  dfm_python/core/helpers.py,sha256=XJvWWEZlUqozBfFNcENKgbOOFkwlm-tGwO-u0LnAf-E,31541
14
- dfm_python/core/numeric.py,sha256=__uwDgRj3TV4S0vXhNc_93kk8U6ewtW8smk0OBZLbdg,26403
15
- dfm_python-0.2.7.dist-info/METADATA,sha256=ZeeKWsk4BkRbihDNjkLkOq-DofODB9RhwG7dxMRIbQI,28599
16
- dfm_python-0.2.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- dfm_python-0.2.7.dist-info/licenses/LICENSE,sha256=6e87br6LCDHjy2hdIR1-sdBQy351qdmSEbOg6jQ7ZXY,1063
18
- dfm_python-0.2.7.dist-info/RECORD,,
14
+ dfm_python/core/numeric.py,sha256=IcIwcKtMhIoJ5-7Mz6UNYwTUtEa5W_ZMnQtF87v0SSc,26431
15
+ dfm_python-0.2.8.dist-info/METADATA,sha256=wY69TZa4fK9I4zY4t7hQUJksWuMCyxtGUS0Nr_C3SPk,28599
16
+ dfm_python-0.2.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
+ dfm_python-0.2.8.dist-info/licenses/LICENSE,sha256=6e87br6LCDHjy2hdIR1-sdBQy351qdmSEbOg6jQ7ZXY,1063
18
+ dfm_python-0.2.8.dist-info/RECORD,,