dfm-python 0.1.2__tar.gz → 0.1.4__tar.gz

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.
Files changed (34) hide show
  1. dfm_python-0.1.4/BUG_ANALYSIS.md +242 -0
  2. dfm_python-0.1.4/CHANGELOG.md +54 -0
  3. dfm_python-0.1.4/PKG-INFO +1010 -0
  4. dfm_python-0.1.4/README.md +968 -0
  5. {dfm_python-0.1.2 → dfm_python-0.1.4}/config/example_config.yaml +60 -30
  6. dfm_python-0.1.4/data/sample_data.csv +193 -0
  7. {dfm_python-0.1.2 → dfm_python-0.1.4}/pyproject.toml +1 -1
  8. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/__init__.py +1 -1
  9. dfm_python-0.1.4/src/dfm_python/__init__.py +60 -0
  10. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/config.py +70 -23
  11. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/data_loader.py +59 -10
  12. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/dfm.py +275 -305
  13. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/kalman.py +68 -16
  14. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/news.py +25 -3
  15. dfm_python-0.1.4/src/dfm_python/utils/__init__.py +27 -0
  16. dfm_python-0.1.4/src/dfm_python/utils/aggregation.py +258 -0
  17. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/utils/data_utils.py +11 -1
  18. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/test_dfm.py +100 -5
  19. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/test_kalman.py +13 -4
  20. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/test_news.py +1 -1
  21. dfm_python-0.1.4/test_comprehensive.py +510 -0
  22. dfm_python-0.1.2/PKG-INFO +0 -395
  23. dfm_python-0.1.2/README.md +0 -353
  24. dfm_python-0.1.2/data/sample_data.csv +0 -133
  25. dfm_python-0.1.2/src/dfm_python/__init__.py +0 -28
  26. dfm_python-0.1.2/src/dfm_python/utils/__init__.py +0 -12
  27. {dfm_python-0.1.2 → dfm_python-0.1.4}/.gitignore +0 -0
  28. {dfm_python-0.1.2 → dfm_python-0.1.4}/.python-version +0 -0
  29. {dfm_python-0.1.2 → dfm_python-0.1.4}/LICENSE +0 -0
  30. {dfm_python-0.1.2 → dfm_python-0.1.4}/MANIFEST.in +0 -0
  31. {dfm_python-0.1.2 → dfm_python-0.1.4}/spec/example_spec.csv +0 -0
  32. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/__init__.py +0 -0
  33. {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/test_synthetic.py +0 -0
  34. {dfm_python-0.1.2 → dfm_python-0.1.4}/uv.lock +0 -0
@@ -0,0 +1,242 @@
1
+ # Bug Analysis Report - dfm-python Package
2
+
3
+ ## Date: 2025-11-07
4
+
5
+ ### Summary
6
+ This document identifies potential bugs found during code inspection of the dfm-python package.
7
+
8
+ ---
9
+
10
+ ## Bug #1: A_temp Variable Scoping Issue (POTENTIAL BUG)
11
+
12
+ ### Location
13
+ - **File:** `src/dfm_python/dfm.py`
14
+ - **Function:** `init_conditions()`
15
+ - **Lines:** 667-693
16
+
17
+ ### Description
18
+ The variable `A_temp` is created inside a `try` block (line 676) but may persist across block iterations, similar to the `ff` bug that was just fixed.
19
+
20
+ ### Code Context
21
+ ```python
22
+ for i in range(n_b): # Block loop
23
+ # ...
24
+ if len(idx_iM) > 0:
25
+ # ...
26
+ if Z_lag.shape[0] > 0 and Z_lag.shape[1] > 0:
27
+ try:
28
+ A_temp = inv(Z_lag.T @ Z_lag) @ Z_lag.T @ z # Line 676
29
+ A_i[:int(r_i), :int(r_i * p)] = A_temp.T
30
+ except (np.linalg.LinAlgError, ValueError) as e:
31
+ # Exception caught - A_temp NOT created
32
+ A_i[:int(r_i), :int(r_i * p)] = 0.0
33
+
34
+ # ...
35
+ if len(z) > 0:
36
+ e = z - Z_lag @ A_temp if 'A_temp' in locals() else z # Line 693
37
+ ```
38
+
39
+ ### Problem
40
+ 1. **Block 0**: If `try` succeeds, `A_temp` is created with dimensions for block 0
41
+ 2. **Block 1**: If `try` fails (exception caught), `A_temp` is NOT created
42
+ 3. **Line 693**: Checks `'A_temp' in locals()` - this returns `True` because `A_temp` from Block 0 still exists!
43
+ 4. **Result**: Uses `A_temp` from Block 0 with `Z_lag` from Block 1 → **dimension mismatch or incorrect computation**
44
+
45
+ ### Impact
46
+ - **Severity:** Medium
47
+ - **Likelihood:** Low (only occurs if one block's try succeeds and next block's try fails)
48
+ - **Affected:** Multi-block models where OLS regression fails for some blocks
49
+
50
+ ### Suggested Fix
51
+ Reset `A_temp = None` at the start of each block iteration, similar to the `ff` fix:
52
+
53
+ ```python
54
+ for i in range(n_b):
55
+ r_i = int(r[i])
56
+ ff = None
57
+ A_temp = None # <-- ADD THIS
58
+
59
+ # ... rest of code ...
60
+
61
+ if len(z) > 0:
62
+ e = z - Z_lag @ A_temp if A_temp is not None else z # <-- CHANGE CHECK
63
+ ```
64
+
65
+ ---
66
+
67
+ ## Bug #2: bl_idxM and bl_idxQ Initialization Issue (POTENTIAL BUG)
68
+
69
+ ### Location
70
+ - **File:** `src/dfm_python/dfm.py`
71
+ - **Function:** `em_step()`
72
+ - **Lines:** 1482-1517
73
+
74
+ ### Description
75
+ `bl_idxM` and `bl_idxQ` are initialized as empty lists `[]` but then used with `np.hstack()` which expects numpy arrays.
76
+
77
+ ### Code Context
78
+ ```python
79
+ bl_idxM = [] # Line 1482 - Python list
80
+ bl_idxQ = [] # Line 1483 - Python list
81
+
82
+ for i in range(num_blocks):
83
+ # ...
84
+ if len(bl_idxM) == 0: # Line 1504
85
+ bl_idxM = bl_col_monthly # numpy array
86
+ bl_idxQ = bl_col_quarterly # numpy array
87
+ else:
88
+ bl_idxM = np.hstack([bl_idxM, bl_col_monthly]) # Line 1508
89
+ bl_idxQ = np.hstack([bl_idxQ, bl_col_quarterly]) # Line 1509
90
+ ```
91
+
92
+ ### Problem
93
+ - First iteration: `bl_idxM` is `[]` (list), then assigned numpy array → OK
94
+ - Second iteration: `bl_idxM` is numpy array, `np.hstack([bl_idxM, ...])` → OK
95
+ - **BUT**: If `num_blocks == 0` or loop doesn't execute, `bl_idxM` remains `[]` (list)
96
+ - Line 1516: `bl_idxM.astype(bool)` on a list → **AttributeError**
97
+
98
+ ### Impact
99
+ - **Severity:** Low
100
+ - **Likelihood:** Very Low (only if `num_blocks == 0`, which shouldn't happen in practice)
101
+ - **Affected:** Edge case with empty block structure
102
+
103
+ ### Suggested Fix
104
+ Initialize as empty numpy arrays instead of lists:
105
+
106
+ ```python
107
+ bl_idxM = np.array([]).reshape(0, n_bl) # Empty 2D array
108
+ bl_idxQ = np.array([]).reshape(0, n_bl) # Empty 2D array
109
+ ```
110
+
111
+ Or check before converting:
112
+
113
+ ```python
114
+ if len(bl_idxM) > 0:
115
+ bl_idxM = bl_idxM.astype(bool)
116
+ bl_idxQ = bl_idxQ.astype(bool)
117
+ else:
118
+ bl_idxM = np.array([]).reshape(0, n_bl).astype(bool)
119
+ bl_idxQ = np.array([]).reshape(0, n_bl).astype(bool)
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Bug #3: F Variable May Be Uninitialized (POTENTIAL BUG)
125
+
126
+ ### Location
127
+ - **File:** `src/dfm_python/dfm.py`
128
+ - **Function:** `init_conditions()`
129
+ - **Lines:** 547-559, 636
130
+
131
+ ### Description
132
+ The variable `F` is created inside `if len(idx_iM) > 0:` block but may be referenced in the `else` branch.
133
+
134
+ ### Code Context
135
+ ```python
136
+ if len(idx_iM) > 0:
137
+ # ...
138
+ F = None # Line 548
139
+ max_lag = max(p + 1, pC)
140
+ for kk in range(max_lag):
141
+ lag_data = f[pC - kk:T - kk, :]
142
+ if F is None:
143
+ F = lag_data
144
+ else:
145
+ F = np.hstack([F, lag_data])
146
+ # ...
147
+ ff = F[:, :int(r_i * pC)] # Line 566
148
+
149
+ # Later...
150
+ else:
151
+ # ...
152
+ if len(idx_iM) > 0 and 'f' in locals() and f is not None:
153
+ F = np.hstack([f] + [np.roll(f, -kk, axis=0) for kk in range(1, max(p+1, pC))]) # Line 636
154
+ ff = F[:, :int(r_i * pC)]
155
+ ```
156
+
157
+ ### Problem
158
+ The `else` branch at line 630 checks `if len(idx_iM) > 0` - but if we're in the `else` branch, it means the outer `if len(idx_iM) > 0:` (line 435) was False, so `len(idx_iM) == 0`. This condition can never be True!
159
+
160
+ ### Impact
161
+ - **Severity:** Low (dead code)
162
+ - **Likelihood:** N/A (code never executes)
163
+ - **Affected:** None (unreachable code)
164
+
165
+ ### Suggested Fix
166
+ Remove the unreachable condition or fix the logic:
167
+
168
+ ```python
169
+ else:
170
+ # If no monthly series, create dummy ff
171
+ ff_padded_i = np.zeros((T, int(pC * r_i)))
172
+ # Remove the unreachable if statement
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Bug #4: Dimension Mismatch in bl_idxQ_i (POTENTIAL BUG)
178
+
179
+ ### Location
180
+ - **File:** `src/dfm_python/dfm.py`
181
+ - **Function:** `em_step()`
182
+ - **Lines:** 1700-1704
183
+
184
+ ### Description
185
+ `bl_idxQ_i` is computed but may have incorrect dimensions when used to reshape empty arrays.
186
+
187
+ ### Code Context
188
+ ```python
189
+ if R_con.size > 0:
190
+ bl_idxQ_i = np.where(bl_idxQ[i, :])[0]
191
+ if len(bl_idxQ_i) > 0 and bl_idxQ_i.max() < R_con.shape[1]:
192
+ # ...
193
+ else:
194
+ R_con_i = np.array([]).reshape(0, len(bl_idxQ_i)) # Line 1700
195
+ q_con_i = np.array([])
196
+ else:
197
+ bl_idxQ_i = np.where(bl_idxQ[i, :])[0]
198
+ R_con_i = np.array([]).reshape(0, len(bl_idxQ_i)) # Line 1704
199
+ q_con_i = np.array([])
200
+ ```
201
+
202
+ ### Problem
203
+ If `bl_idxQ_i` is empty (`len(bl_idxQ_i) == 0`), then `reshape(0, 0)` creates a 0x0 array, which may cause issues in subsequent operations expecting a specific shape.
204
+
205
+ ### Impact
206
+ - **Severity:** Low
207
+ - **Likelihood:** Low (only if no quarterly series in block)
208
+ - **Affected:** Blocks with no quarterly series
209
+
210
+ ### Suggested Fix
211
+ Add a check for empty `bl_idxQ_i`:
212
+
213
+ ```python
214
+ if len(bl_idxQ_i) > 0:
215
+ R_con_i = np.array([]).reshape(0, len(bl_idxQ_i))
216
+ else:
217
+ R_con_i = np.array([]).reshape(0, 1) # Default to 1 column
218
+ ```
219
+
220
+ ---
221
+
222
+ ## Recommendations
223
+
224
+ ### Priority Order
225
+ 1. **Bug #1 (A_temp)**: Fix immediately - similar to the `ff` bug that was just fixed
226
+ 2. **Bug #2 (bl_idxM/bl_idxQ)**: Fix for robustness - edge case but easy to fix
227
+ 3. **Bug #3 (F variable)**: Clean up dead code
228
+ 4. **Bug #4 (bl_idxQ_i)**: Low priority - may not cause issues in practice
229
+
230
+ ### Testing Recommendations
231
+ 1. Create test case for multi-block model where one block's OLS regression fails
232
+ 2. Test edge case with `num_blocks == 0` (should be caught by validation, but test anyway)
233
+ 3. Test blocks with no quarterly series to verify Bug #4 doesn't cause issues
234
+
235
+ ---
236
+
237
+ ## Notes
238
+ - All bugs found are potential issues that may not manifest in typical usage
239
+ - Bug #1 is the most critical as it's similar to the recently fixed `ff` scoping bug
240
+ - Most issues are related to variable scoping across loop iterations
241
+ - Consider adding more explicit variable resets at loop starts for safety
242
+
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ ## [0.1.4] - 2024-11-07
4
+
5
+ ### Fixed
6
+ - Fixed critical bug: `ff` variable scoping issue in `init_conditions()` causing dimension mismatch errors in multi-block models with different factor counts
7
+ - Fixed `A_temp` variable scoping issue that could cause incorrect computations when OLS regression fails for some blocks
8
+ - Fixed `bl_idxM` and `bl_idxQ` initialization issue that could cause AttributeError when no blocks are processed
9
+ - Removed unreachable code in `init_conditions()` else branch
10
+ - Improved empty array handling in `em_step()` for quarterly series constraints
11
+
12
+ ### Changed
13
+ - Enhanced variable scoping safety by resetting block-specific variables at start of each iteration
14
+ - Improved error handling for edge cases with empty arrays
15
+
16
+ ## [0.1.3] - 2024-11-07
17
+
18
+ ### Added
19
+ - Comprehensive README with detailed inputs/outputs documentation
20
+ - Clock-based mixed-frequency framework documentation
21
+ - Enhanced module-level docstrings throughout the codebase
22
+ - Detailed API reference with examples
23
+ - Improved error messages with context and solutions
24
+
25
+ ### Changed
26
+ - Optimized code performance (removed redundant computations)
27
+ - Improved memory usage (replaced unnecessary copies with views)
28
+ - Enhanced code documentation and comments
29
+ - Updated package description and metadata
30
+
31
+ ### Fixed
32
+ - Removed duplicate `frequencies_array` computation
33
+ - Fixed `optNaN` dictionary mutation issue
34
+ - Improved code organization and clarity
35
+
36
+ ### Documentation
37
+ - Complete input/output specifications
38
+ - 5 comprehensive usage examples
39
+ - Troubleshooting guide with solutions
40
+ - Architecture overview
41
+ - Clock-based framework explanation
42
+
43
+ ## [0.1.2] - Previous release
44
+
45
+ ### Fixed
46
+ - Fixed `ModuleNotFoundError: No module named 'utils'` by moving utils into dfm_python package
47
+ - Improved import paths and package structure
48
+
49
+ ## [0.1.1] - Initial release
50
+
51
+ - Initial PyPI release
52
+ - Core DFM estimation functionality
53
+ - Mixed-frequency data support
54
+ - News decomposition