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.
- dfm_python-0.1.4/BUG_ANALYSIS.md +242 -0
- dfm_python-0.1.4/CHANGELOG.md +54 -0
- dfm_python-0.1.4/PKG-INFO +1010 -0
- dfm_python-0.1.4/README.md +968 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/config/example_config.yaml +60 -30
- dfm_python-0.1.4/data/sample_data.csv +193 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/pyproject.toml +1 -1
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/__init__.py +1 -1
- dfm_python-0.1.4/src/dfm_python/__init__.py +60 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/config.py +70 -23
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/data_loader.py +59 -10
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/dfm.py +275 -305
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/kalman.py +68 -16
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/news.py +25 -3
- dfm_python-0.1.4/src/dfm_python/utils/__init__.py +27 -0
- dfm_python-0.1.4/src/dfm_python/utils/aggregation.py +258 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/dfm_python/utils/data_utils.py +11 -1
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/test_dfm.py +100 -5
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/test_kalman.py +13 -4
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/test_news.py +1 -1
- dfm_python-0.1.4/test_comprehensive.py +510 -0
- dfm_python-0.1.2/PKG-INFO +0 -395
- dfm_python-0.1.2/README.md +0 -353
- dfm_python-0.1.2/data/sample_data.csv +0 -133
- dfm_python-0.1.2/src/dfm_python/__init__.py +0 -28
- dfm_python-0.1.2/src/dfm_python/utils/__init__.py +0 -12
- {dfm_python-0.1.2 → dfm_python-0.1.4}/.gitignore +0 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/.python-version +0 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/LICENSE +0 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/MANIFEST.in +0 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/spec/example_spec.csv +0 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/__init__.py +0 -0
- {dfm_python-0.1.2 → dfm_python-0.1.4}/src/test/test_synthetic.py +0 -0
- {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
|