Kolesnikov Dmitry commited on
Commit
9ee5ee8
·
1 Parent(s): e5e9dcb

feat: Реализованное 3 задание

Browse files
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .idea
Dockerfile CHANGED
@@ -10,6 +10,7 @@ RUN apt-get update && apt-get install -y \
10
 
11
  COPY requirements.txt ./
12
  COPY src/ ./src/
 
13
 
14
  RUN pip3 install -r requirements.txt
15
 
 
10
 
11
  COPY requirements.txt ./
12
  COPY src/ ./src/
13
+ COPY russia_covid_dataset.csv/ ./
14
 
15
  RUN pip3 install -r requirements.txt
16
 
requirements.txt CHANGED
@@ -1,3 +1,16 @@
1
- altair
2
  pandas
3
- streamlit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
  pandas
3
+ numpy
4
+ plotly
5
+ statsmodels
6
+ scipy
7
+ scikit-learn
8
+ pytz
9
+ pmdarima
10
+ arch
11
+ prophet
12
+ lightgbm
13
+ xgboost
14
+ torch
15
+ tensorflow
16
+ tbats
russia_covid_dataset.csv ADDED
@@ -0,0 +1,821 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Date,DailyNewCases,ActiveCases,DailyNewDeaths
2
+ 2020-2-15,,0.0,
3
+ 2020-2-16,0.0,0.0,
4
+ 2020-2-17,0.0,0.0,
5
+ 2020-2-18,0.0,0.0,
6
+ 2020-2-19,0.0,0.0,
7
+ 2020-2-20,0.0,0.0,0.0
8
+ 2020-2-21,0.0,0.0,0.0
9
+ 2020-2-22,0.0,0.0,0.0
10
+ 2020-2-23,0.0,0.0,0.0
11
+ 2020-2-24,0.0,0.0,0.0
12
+ 2020-2-25,0.0,0.0,0.0
13
+ 2020-2-26,0.0,0.0,0.0
14
+ 2020-2-27,0.0,0.0,0.0
15
+ 2020-2-28,0.0,0.0,0.0
16
+ 2020-2-29,0.0,0.0,0.0
17
+ 2020-3-01,0.0,0.0,0.0
18
+ 2020-3-02,1.0,1.0,0.0
19
+ 2020-3-03,0.0,1.0,0.0
20
+ 2020-3-04,0.0,1.0,0.0
21
+ 2020-3-05,4.0,5.0,0.0
22
+ 2020-3-06,6.0,11.0,0.0
23
+ 2020-3-07,1.0,11.0,0.0
24
+ 2020-3-08,3.0,14.0,0.0
25
+ 2020-3-09,3.0,17.0,0.0
26
+ 2020-3-10,0.0,17.0,0.0
27
+ 2020-3-11,8.0,25.0,0.0
28
+ 2020-3-12,6.0,31.0,0.0
29
+ 2020-3-13,11.0,37.0,0.0
30
+ 2020-3-14,14.0,51.0,0.0
31
+ 2020-3-15,4.0,55.0,0.0
32
+ 2020-3-16,30.0,85.0,0.0
33
+ 2020-3-17,21.0,106.0,0.0
34
+ 2020-3-18,33.0,139.0,0.0
35
+ 2020-3-19,52.0,190.0,1.0
36
+ 2020-3-20,54.0,240.0,0.0
37
+ 2020-3-21,53.0,289.0,0.0
38
+ 2020-3-22,61.0,350.0,0.0
39
+ 2020-3-23,71.0,420.0,0.0
40
+ 2020-3-24,57.0,472.0,0.0
41
+ 2020-3-25,163.0,626.0,2.0
42
+ 2020-3-26,182.0,799.0,0.0
43
+ 2020-3-27,196.0,987.0,1.0
44
+ 2020-3-28,228.0,1211.0,0.0
45
+ 2020-3-29,270.0,1462.0,4.0
46
+ 2020-3-30,302.0,1761.0,1.0
47
+ 2020-3-31,501.0,2199.0,8.0
48
+ 2020-4-01,440.0,2563.0,7.0
49
+ 2020-4-02,771.0,3283.0,6.0
50
+ 2020-4-03,601.0,3834.0,4.0
51
+ 2020-4-04,582.0,4355.0,9.0
52
+ 2020-4-05,658.0,4989.0,2.0
53
+ 2020-4-06,954.0,5890.0,2.0
54
+ 2020-4-07,1154.0,6945.0,11.0
55
+ 2020-4-08,1175.0,8029.0,5.0
56
+ 2020-4-09,1459.0,9357.0,13.0
57
+ 2020-4-10,1786.0,11028.0,18.0
58
+ 2020-4-11,1667.0,12433.0,12.0
59
+ 2020-4-12,2186.0,14349.0,24.0
60
+ 2020-4-13,2558.0,16710.0,18.0
61
+ 2020-4-14,2774.0,19238.0,22.0
62
+ 2020-4-15,3388.0,22306.0,28.0
63
+ 2020-4-16,3448.0,25402.0,34.0
64
+ 2020-4-17,4070.0,29145.0,41.0
65
+ 2020-4-18,4785.0,33423.0,40.0
66
+ 2020-4-19,6060.0,39201.0,48.0
67
+ 2020-4-20,4268.0,43270.0,44.0
68
+ 2020-4-21,5642.0,48434.0,51.0
69
+ 2020-4-22,5236.0,53066.0,57.0
70
+ 2020-4-23,4774.0,57327.0,42.0
71
+ 2020-4-24,5849.0,62439.0,60.0
72
+ 2020-4-25,5966.0,67657.0,66.0
73
+ 2020-4-26,6361.0,73435.0,66.0
74
+ 2020-4-27,6198.0,79007.0,47.0
75
+ 2020-4-28,6411.0,84235.0,73.0
76
+ 2020-4-29,5841.0,88141.0,105.0
77
+ 2020-4-30,7099.0,93806.0,101.0
78
+ 2020-5-01,7933.0,100042.0,96.0
79
+ 2020-5-02,9623.0,107819.0,53.0
80
+ 2020-5-03,10633.0,116768.0,58.0
81
+ 2020-5-04,10581.0,125817.0,76.0
82
+ 2020-5-05,10102.0,134054.0,95.0
83
+ 2020-5-06,10559.0,143065.0,86.0
84
+ 2020-5-07,11231.0,151732.0,88.0
85
+ 2020-5-08,10699.0,159528.0,98.0
86
+ 2020-5-09,10817.0,164933.0,104.0
87
+ 2020-5-10,11012.0,173467.0,88.0
88
+ 2020-5-11,11656.0,179534.0,94.0
89
+ 2020-5-12,10899.0,186615.0,107.0
90
+ 2020-5-13,10028.0,192056.0,96.0
91
+ 2020-5-14,9974.0,196410.0,93.0
92
+ 2020-5-15,10598.0,202199.0,113.0
93
+ 2020-5-16,9200.0,206340.0,119.0
94
+ 2020-5-17,9709.0,211748.0,94.0
95
+ 2020-5-18,8926.0,217747.0,91.0
96
+ 2020-5-19,9263.0,220974.0,115.0
97
+ 2020-5-20,8764.0,220341.0,135.0
98
+ 2020-5-21,8849.0,221774.0,127.0
99
+ 2020-5-22,8894.0,223374.0,150.0
100
+ 2020-5-23,9434.0,224558.0,139.0
101
+ 2020-5-24,8599.0,227641.0,153.0
102
+ 2020-5-25,8946.0,230996.0,92.0
103
+ 2020-5-26,8915.0,227406.0,174.0
104
+ 2020-5-27,8338.0,224504.0,161.0
105
+ 2020-5-28,8371.0,223916.0,174.0
106
+ 2020-5-29,8572.0,223992.0,232.0
107
+ 2020-5-30,8952.0,224551.0,181.0
108
+ 2020-5-31,9268.0,229267.0,138.0
109
+ 2020-6-01,9035.0,234146.0,162.0
110
+ 2020-6-02,8863.0,231719.0,182.0
111
+ 2020-6-03,8536.0,231105.0,178.0
112
+ 2020-6-04,8831.0,231101.0,169.0
113
+ 2020-6-05,8726.0,231626.0,144.0
114
+ 2020-6-06,8855.0,231576.0,197.0
115
+ 2020-6-07,8984.0,235083.0,134.0
116
+ 2020-6-08,8985.0,239999.0,112.0
117
+ 2020-6-09,8595.0,236714.0,171.0
118
+ 2020-6-10,8404.0,234516.0,216.0
119
+ 2020-6-11,8779.0,234754.0,174.0
120
+ 2020-6-12,8987.0,235338.0,183.0
121
+ 2020-6-13,8706.0,238659.0,114.0
122
+ 2020-6-14,8835.0,241966.0,119.0
123
+ 2020-6-15,8246.0,245580.0,143.0
124
+ 2020-6-16,8248.0,243868.0,193.0
125
+ 2020-6-17,7843.0,241481.0,194.0
126
+ 2020-6-18,7790.0,239468.0,182.0
127
+ 2020-6-19,7972.0,236816.0,181.0
128
+ 2020-6-20,7889.0,234358.0,161.0
129
+ 2020-6-21,7728.0,236858.0,109.0
130
+ 2020-6-22,7600.0,239658.0,95.0
131
+ 2020-6-23,7425.0,234917.0,153.0
132
+ 2020-6-24,7176.0,229546.0,154.0
133
+ 2020-6-25,7113.0,230225.0,92.0
134
+ 2020-6-26,6800.0,227861.0,176.0
135
+ 2020-6-27,6852.0,225325.0,188.0
136
+ 2020-6-28,6791.0,226277.0,104.0
137
+ 2020-6-29,6719.0,228560.0,93.0
138
+ 2020-6-30,6693.0,225879.0,154.0
139
+ 2020-7-01,6556.0,221938.0,216.0
140
+ 2020-7-02,6760.0,222504.0,147.0
141
+ 2020-7-03,6718.0,220131.0,176.0
142
+ 2020-7-04,6632.0,217609.0,168.0
143
+ 2020-7-05,6736.0,220340.0,134.0
144
+ 2020-7-06,6611.0,223237.0,135.0
145
+ 2020-7-07,6368.0,219856.0,198.0
146
+ 2020-7-08,6562.0,217614.0,173.0
147
+ 2020-7-09,6509.0,215142.0,176.0
148
+ 2020-7-10,6635.0,213851.0,174.0
149
+ 2020-7-11,6611.0,211896.0,188.0
150
+ 2020-7-12,6615.0,214766.0,130.0
151
+ 2020-7-13,6537.0,218239.0,104.0
152
+ 2020-7-14,6248.0,215508.0,175.0
153
+ 2020-7-15,6422.0,211350.0,156.0
154
+ 2020-7-16,6428.0,209168.0,167.0
155
+ 2020-7-17,6406.0,207707.0,186.0
156
+ 2020-7-18,6234.0,206327.0,124.0
157
+ 2020-7-19,6109.0,208860.0,95.0
158
+ 2020-7-20,5940.0,211457.0,85.0
159
+ 2020-7-21,5842.0,208364.0,153.0
160
+ 2020-7-22,5862.0,204392.0,165.0
161
+ 2020-7-23,5848.0,201816.0,147.0
162
+ 2020-7-24,5811.0,199029.0,154.0
163
+ 2020-7-25,5871.0,196388.0,146.0
164
+ 2020-7-26,5765.0,198966.0,77.0
165
+ 2020-7-27,5635.0,201437.0,85.0
166
+ 2020-7-28,5395.0,197794.0,150.0
167
+ 2020-7-29,5475.0,194984.0,169.0
168
+ 2020-7-30,5509.0,191042.0,129.0
169
+ 2020-7-31,5482.0,187608.0,161.0
170
+ 2020-8-01,5462.0,184861.0,95.0
171
+ 2020-8-02,5427.0,186569.0,70.0
172
+ 2020-8-03,5394.0,188464.0,79.0
173
+ 2020-8-04,5159.0,185601.0,144.0
174
+ 2020-8-05,5204.0,183111.0,139.0
175
+ 2020-8-06,5267.0,180931.0,116.0
176
+ 2020-8-07,5241.0,178818.0,119.0
177
+ 2020-8-08,5212.0,177286.0,129.0
178
+ 2020-8-09,5189.0,179183.0,77.0
179
+ 2020-8-10,5118.0,180972.0,70.0
180
+ 2020-8-11,4945.0,179293.0,130.0
181
+ 2020-8-12,5102.0,177143.0,129.0
182
+ 2020-8-13,5057.0,175978.0,124.0
183
+ 2020-8-14,5065.0,174361.0,114.0
184
+ 2020-8-15,5061.0,172856.0,119.0
185
+ 2020-8-16,4969.0,174200.0,68.0
186
+ 2020-8-17,4892.0,175904.0,55.0
187
+ 2020-8-18,4748.0,173993.0,132.0
188
+ 2020-8-19,4828.0,171909.0,117.0
189
+ 2020-8-20,4785.0,170494.0,110.0
190
+ 2020-8-21,4870.0,169457.0,90.0
191
+ 2020-8-22,4921.0,168110.0,121.0
192
+ 2020-8-23,4852.0,169727.0,73.0
193
+ 2020-8-24,4744.0,171950.0,65.0
194
+ 2020-8-25,4696.0,169874.0,120.0
195
+ 2020-8-26,4676.0,168032.0,115.0
196
+ 2020-8-27,4711.0,166211.0,121.0
197
+ 2020-8-28,4829.0,165025.0,110.0
198
+ 2020-8-29,4941.0,163938.0,111.0
199
+ 2020-8-30,4980.0,166251.0,68.0
200
+ 2020-8-31,4993.0,168756.0,83.0
201
+ 2020-9-01,4729.0,167044.0,123.0
202
+ 2020-9-02,4952.0,166417.0,115.0
203
+ 2020-9-03,4995.0,165532.0,114.0
204
+ 2020-9-04,5110.0,164709.0,121.0
205
+ 2020-9-05,5205.0,164425.0,110.0
206
+ 2020-9-06,5195.0,166736.0,61.0
207
+ 2020-9-07,5185.0,169542.0,51.0
208
+ 2020-9-08,5099.0,167747.0,122.0
209
+ 2020-9-09,5218.0,166414.0,142.0
210
+ 2020-9-10,5363.0,165734.0,128.0
211
+ 2020-9-11,5504.0,165402.0,102.0
212
+ 2020-9-12,5488.0,165343.0,119.0
213
+ 2020-9-13,5449.0,168008.0,94.0
214
+ 2020-9-14,5509.0,170985.0,57.0
215
+ 2020-9-15,5529.0,170759.0,150.0
216
+ 2020-9-16,5670.0,170488.0,132.0
217
+ 2020-9-17,5762.0,170352.0,144.0
218
+ 2020-9-18,5905.0,170784.0,134.0
219
+ 2020-9-19,6065.0,171450.0,144.0
220
+ 2020-9-20,6148.0,174624.0,79.0
221
+ 2020-9-21,6196.0,178133.0,71.0
222
+ 2020-9-22,6215.0,178212.0,160.0
223
+ 2020-9-23,6431.0,178743.0,150.0
224
+ 2020-9-24,6595.0,179059.0,149.0
225
+ 2020-9-25,7212.0,181846.0,108.0
226
+ 2020-9-26,7523.0,183196.0,169.0
227
+ 2020-9-27,7867.0,187896.0,99.0
228
+ 2020-9-28,8135.0,193268.0,61.0
229
+ 2020-9-29,8232.0,194861.0,160.0
230
+ 2020-9-30,8481.0,197307.0,177.0
231
+ 2020-10-01,8945.0,200098.0,169.0
232
+ 2020-10-02,9412.0,203270.0,186.0
233
+ 2020-10-03,9859.0,207392.0,174.0
234
+ 2020-10-04,10499.0,214500.0,107.0
235
+ 2020-10-05,10888.0,222090.0,117.0
236
+ 2020-10-06,11615.0,227265.0,188.0
237
+ 2020-10-07,11115.0,231479.0,202.0
238
+ 2020-10-08,11493.0,235727.0,191.0
239
+ 2020-10-09,12126.0,240560.0,201.0
240
+ 2020-10-10,12846.0,246428.0,197.0
241
+ 2020-10-11,13634.0,255679.0,143.0
242
+ 2020-10-12,13592.0,265353.0,125.0
243
+ 2020-10-13,13868.0,271427.0,244.0
244
+ 2020-10-14,14231.0,277499.0,239.0
245
+ 2020-10-15,13754.0,282575.0,286.0
246
+ 2020-10-16,15150.0,289008.0,232.0
247
+ 2020-10-17,14922.0,295034.0,279.0
248
+ 2020-10-18,15099.0,304571.0,185.0
249
+ 2020-10-19,15982.0,315046.0,179.0
250
+ 2020-10-20,16319.0,321392.0,269.0
251
+ 2020-10-21,15700.0,325823.0,317.0
252
+ 2020-10-22,15971.0,330076.0,290.0
253
+ 2020-10-23,17340.0,335870.0,283.0
254
+ 2020-10-24,16521.0,340528.0,296.0
255
+ 2020-10-25,16710.0,349305.0,229.0
256
+ 2020-10-26,17347.0,358859.0,219.0
257
+ 2020-10-27,16550.0,362245.0,320.0
258
+ 2020-10-28,16202.0,365740.0,346.0
259
+ 2020-10-29,17717.0,368351.0,366.0
260
+ 2020-10-30,18283.0,371760.0,355.0
261
+ 2020-10-31,18140.0,374712.0,334.0
262
+ 2020-11-01,18665.0,382873.0,245.0
263
+ 2020-11-02,18257.0,390532.0,238.0
264
+ 2020-11-03,18648.0,393494.0,355.0
265
+ 2020-11-04,19768.0,397306.0,389.0
266
+ 2020-11-05,19404.0,404180.0,292.0
267
+ 2020-11-06,20582.0,407429.0,378.0
268
+ 2020-11-07,20396.0,410658.0,364.0
269
+ 2020-11-08,20498.0,419378.0,286.0
270
+ 2020-11-09,21798.0,430198.0,256.0
271
+ 2020-11-10,20977.0,435207.0,368.0
272
+ 2020-11-11,19851.0,436010.0,432.0
273
+ 2020-11-12,21608.0,438368.0,439.0
274
+ 2020-11-13,21983.0,441205.0,411.0
275
+ 2020-11-14,22702.0,444890.0,391.0
276
+ 2020-11-15,22572.0,452654.0,352.0
277
+ 2020-11-16,22778.0,461265.0,303.0
278
+ 2020-11-17,22410.0,461178.0,442.0
279
+ 2020-11-18,20985.0,456528.0,456.0
280
+ 2020-11-19,23610.0,454102.0,463.0
281
+ 2020-11-20,24318.0,453201.0,461.0
282
+ 2020-11-21,24822.0,451535.0,467.0
283
+ 2020-11-22,24581.0,457707.0,401.0
284
+ 2020-11-23,25173.0,466517.0,361.0
285
+ 2020-11-24,24326.0,467126.0,491.0
286
+ 2020-11-25,23675.0,464546.0,507.0
287
+ 2020-11-26,25487.0,464436.0,524.0
288
+ 2020-11-27,27543.0,464801.0,496.0
289
+ 2020-11-28,27100.0,464095.0,510.0
290
+ 2020-11-29,26683.0,468332.0,459.0
291
+ 2020-11-30,26338.0,477055.0,368.0
292
+ 2020-12-01,26402.0,478125.0,569.0
293
+ 2020-12-02,25345.0,475999.0,589.0
294
+ 2020-12-03,28145.0,474088.0,554.0
295
+ 2020-12-04,27403.0,472021.0,569.0
296
+ 2020-12-05,28782.0,472651.0,508.0
297
+ 2020-12-06,29039.0,479891.0,457.0
298
+ 2020-12-07,28142.0,488727.0,456.0
299
+ 2020-12-08,26097.0,489324.0,562.0
300
+ 2020-12-09,26190.0,488689.0,559.0
301
+ 2020-12-10,27927.0,490177.0,562.0
302
+ 2020-12-11,28585.0,491978.0,613.0
303
+ 2020-12-12,28137.0,493437.0,560.0
304
+ 2020-12-13,28080.0,500752.0,488.0
305
+ 2020-12-14,27328.0,509068.0,450.0
306
+ 2020-12-15,26689.0,510367.0,577.0
307
+ 2020-12-16,26509.0,509790.0,596.0
308
+ 2020-12-17,28214.0,510977.0,587.0
309
+ 2020-12-18,28552.0,512825.0,611.0
310
+ 2020-12-19,28209.0,514340.0,585.0
311
+ 2020-12-20,28948.0,521862.0,511.0
312
+ 2020-12-21,29350.0,531014.0,493.0
313
+ 2020-12-22,28776.0,535071.0,561.0
314
+ 2020-12-23,27250.0,537325.0,549.0
315
+ 2020-12-24,29935.0,539735.0,635.0
316
+ 2020-12-25,29018.0,540793.0,563.0
317
+ 2020-12-26,29258.0,541299.0,567.0
318
+ 2020-12-27,28284.0,544641.0,552.0
319
+ 2020-12-28,27787.0,551461.0,487.0
320
+ 2020-12-29,27002.0,553027.0,562.0
321
+ 2020-12-30,26513.0,549706.0,599.0
322
+ 2020-12-31,27747.0,547938.0,593.0
323
+ 2021-1-01,27039.0,548643.0,536.0
324
+ 2021-1-02,26301.0,555600.0,447.0
325
+ 2021-1-03,24150.0,559399.0,504.0
326
+ 2021-1-04,23351.0,561114.0,482.0
327
+ 2021-1-05,24246.0,562210.0,518.0
328
+ 2021-1-06,24217.0,562927.0,445.0
329
+ 2021-1-07,23541.0,562233.0,506.0
330
+ 2021-1-08,23652.0,563754.0,454.0
331
+ 2021-1-09,23309.0,562913.0,470.0
332
+ 2021-1-10,22851.0,561228.0,456.0
333
+ 2021-1-11,23315.0,562321.0,436.0
334
+ 2021-1-12,22934.0,559969.0,531.0
335
+ 2021-1-13,22850.0,553595.0,566.0
336
+ 2021-1-14,24763.0,549832.0,570.0
337
+ 2021-1-15,24715.0,546356.0,555.0
338
+ 2021-1-16,24092.0,542547.0,590.0
339
+ 2021-1-17,23586.0,542212.0,481.0
340
+ 2021-1-18,22857.0,546265.0,471.0
341
+ 2021-1-19,21734.0,544151.0,586.0
342
+ 2021-1-20,21152.0,539416.0,597.0
343
+ 2021-1-21,21887.0,533789.0,612.0
344
+ 2021-1-22,21513.0,527404.0,580.0
345
+ 2021-1-23,20921.0,519987.0,559.0
346
+ 2021-1-24,21127.0,518178.0,491.0
347
+ 2021-1-25,19290.0,518009.0,456.0
348
+ 2021-1-26,18241.0,511888.0,564.0
349
+ 2021-1-27,17741.0,501113.0,594.0
350
+ 2021-1-28,19138.0,492901.0,575.0
351
+ 2021-1-29,19238.0,485401.0,534.0
352
+ 2021-1-30,19032.0,479419.0,512.0
353
+ 2021-1-31,18359.0,477253.0,485.0
354
+ 2021-2-01,17648.0,476295.0,437.0
355
+ 2021-2-02,16643.0,470027.0,539.0
356
+ 2021-2-03,16474.0,461153.0,526.0
357
+ 2021-2-04,16714.0,452800.0,521.0
358
+ 2021-2-05,16688.0,445379.0,527.0
359
+ 2021-2-06,16627.0,438678.0,497.0
360
+ 2021-2-07,16048.0,434410.0,432.0
361
+ 2021-2-08,15916.0,434038.0,407.0
362
+ 2021-2-09,15019.0,426732.0,530.0
363
+ 2021-2-10,14494.0,418115.0,536.0
364
+ 2021-2-11,15038.0,410639.0,553.0
365
+ 2021-2-12,15089.0,404501.0,507.0
366
+ 2021-2-13,14861.0,400095.0,502.0
367
+ 2021-2-14,14185.0,398656.0,430.0
368
+ 2021-2-15,14207.0,398534.0,394.0
369
+ 2021-2-16,13233.0,393681.0,459.0
370
+ 2021-2-17,12828.0,388123.0,467.0
371
+ 2021-2-18,13447.0,382360.0,480.0
372
+ 2021-2-19,13433.0,376686.0,470.0
373
+ 2021-2-20,12953.0,371675.0,480.0
374
+ 2021-2-21,12742.0,367988.0,417.0
375
+ 2021-2-22,12604.0,367312.0,337.0
376
+ 2021-2-23,11823.0,365762.0,417.0
377
+ 2021-2-24,11749.0,364910.0,383.0
378
+ 2021-2-25,11198.0,359560.0,446.0
379
+ 2021-2-26,11086.0,354496.0,428.0
380
+ 2021-2-27,11534.0,349571.0,439.0
381
+ 2021-2-28,11359.0,348160.0,379.0
382
+ 2021-3-01,11571.0,348121.0,333.0
383
+ 2021-3-02,10565.0,343279.0,441.0
384
+ 2021-3-03,10535.0,337668.0,452.0
385
+ 2021-3-04,11385.0,332455.0,475.0
386
+ 2021-3-05,11024.0,327553.0,462.0
387
+ 2021-3-06,11022.0,323107.0,441.0
388
+ 2021-3-07,10595.0,321758.0,368.0
389
+ 2021-3-08,10253.0,321310.0,379.0
390
+ 2021-3-09,9445.0,320488.0,336.0
391
+ 2021-3-10,9079.0,315751.0,466.0
392
+ 2021-3-11,9270.0,310556.0,459.0
393
+ 2021-3-12,9794.0,306368.0,486.0
394
+ 2021-3-13,9908.0,302933.0,475.0
395
+ 2021-3-14,10083.0,303209.0,395.0
396
+ 2021-3-15,9437.0,303975.0,404.0
397
+ 2021-3-16,9393.0,302281.0,443.0
398
+ 2021-3-17,8998.0,300097.0,427.0
399
+ 2021-3-18,9803.0,297379.0,460.0
400
+ 2021-3-19,9699.0,294298.0,443.0
401
+ 2021-3-20,9632.0,292259.0,392.0
402
+ 2021-3-21,9299.0,292444.0,371.0
403
+ 2021-3-22,9284.0,293577.0,361.0
404
+ 2021-3-23,8457.0,290747.0,427.0
405
+ 2021-3-24,8861.0,288852.0,401.0
406
+ 2021-3-25,9221.0,286799.0,393.0
407
+ 2021-3-26,9167.0,284681.0,405.0
408
+ 2021-3-27,8885.0,282842.0,387.0
409
+ 2021-3-28,9088.0,282964.0,336.0
410
+ 2021-3-29,8711.0,284102.0,293.0
411
+ 2021-3-30,8277.0,282382.0,409.0
412
+ 2021-3-31,8275.0,280073.0,408.0
413
+ 2021-4-01,9169.0,278612.0,383.0
414
+ 2021-4-02,8792.0,277172.0,400.0
415
+ 2021-4-03,9021.0,276191.0,384.0
416
+ 2021-4-04,8817.0,276439.0,357.0
417
+ 2021-4-05,8646.0,277690.0,343.0
418
+ 2021-4-06,8328.0,276727.0,389.0
419
+ 2021-4-07,8294.0,275202.0,374.0
420
+ 2021-4-08,8672.0,273951.0,365.0
421
+ 2021-4-09,9150.0,273037.0,402.0
422
+ 2021-4-10,8704.0,271760.0,402.0
423
+ 2021-4-11,8702.0,272895.0,337.0
424
+ 2021-4-12,8320.0,274282.0,277.0
425
+ 2021-4-13,8173.0,272506.0,338.0
426
+ 2021-4-14,8326.0,270986.0,399.0
427
+ 2021-4-15,8944.0,269307.0,398.0
428
+ 2021-4-16,8995.0,268796.0,397.0
429
+ 2021-4-17,9321.0,268887.0,398.0
430
+ 2021-4-18,8632.0,269739.0,389.0
431
+ 2021-4-19,8589.0,271164.0,346.0
432
+ 2021-4-20,8164.0,269318.0,379.0
433
+ 2021-4-21,8271.0,267546.0,399.0
434
+ 2021-4-22,8996.0,267211.0,397.0
435
+ 2021-4-23,8840.0,266246.0,398.0
436
+ 2021-4-24,8828.0,265421.0,399.0
437
+ 2021-4-25,8780.0,266329.0,332.0
438
+ 2021-4-26,8803.0,268145.0,356.0
439
+ 2021-4-27,8053.0,267767.0,392.0
440
+ 2021-4-28,7848.0,266808.0,387.0
441
+ 2021-4-29,9284.0,267286.0,364.0
442
+ 2021-4-30,8731.0,267214.0,397.0
443
+ 2021-5-01,9270.0,267455.0,392.0
444
+ 2021-5-02,8697.0,268471.0,342.0
445
+ 2021-5-03,8489.0,270257.0,336.0
446
+ 2021-5-04,7770.0,270935.0,337.0
447
+ 2021-5-05,7975.0,271044.0,360.0
448
+ 2021-5-06,7639.0,270544.0,351.0
449
+ 2021-5-07,8386.0,270532.0,376.0
450
+ 2021-5-08,8329.0,270236.0,370.0
451
+ 2021-5-09,8419.0,270804.0,334.0
452
+ 2021-5-10,8465.0,272174.0,321.0
453
+ 2021-5-11,8115.0,272951.0,329.0
454
+ 2021-5-12,8217.0,272199.0,355.0
455
+ 2021-5-13,8380.0,270838.0,392.0
456
+ 2021-5-14,9462.0,270151.0,393.0
457
+ 2021-5-15,8790.0,268711.0,364.0
458
+ 2021-5-16,8554.0,268301.0,391.0
459
+ 2021-5-17,9328.0,270108.0,340.0
460
+ 2021-5-18,8183.0,268955.0,364.0
461
+ 2021-5-19,7920.0,266924.0,390.0
462
+ 2021-5-20,9232.0,265777.0,396.0
463
+ 2021-5-21,8937.0,264986.0,378.0
464
+ 2021-5-22,8709.0,263964.0,386.0
465
+ 2021-5-23,8951.0,265261.0,357.0
466
+ 2021-5-24,8406.0,266898.0,319.0
467
+ 2021-5-25,7884.0,265646.0,393.0
468
+ 2021-5-26,8373.0,264478.0,406.0
469
+ 2021-5-27,9039.0,263356.0,402.0
470
+ 2021-5-28,9252.0,262819.0,404.0
471
+ 2021-5-29,9289.0,262457.0,401.0
472
+ 2021-5-30,9694.0,264410.0,355.0
473
+ 2021-5-31,8475.0,265831.0,339.0
474
+ 2021-6-01,9500.0,265965.0,372.0
475
+ 2021-6-02,8832.0,265383.0,394.0
476
+ 2021-6-03,8933.0,264540.0,393.0
477
+ 2021-6-04,8947.0,264580.0,377.0
478
+ 2021-6-05,9145.0,264761.0,399.0
479
+ 2021-6-06,9163.0,266204.0,351.0
480
+ 2021-6-07,9429.0,268547.0,330.0
481
+ 2021-6-08,9977.0,269262.0,379.0
482
+ 2021-6-09,10407.0,269456.0,399.0
483
+ 2021-6-10,11699.0,270676.0,383.0
484
+ 2021-6-11,12505.0,272597.0,396.0
485
+ 2021-6-12,13510.0,275722.0,399.0
486
+ 2021-6-13,14723.0,280922.0,357.0
487
+ 2021-6-14,13721.0,285960.0,371.0
488
+ 2021-6-15,14185.0,291169.0,379.0
489
+ 2021-6-16,13397.0,293914.0,396.0
490
+ 2021-6-17,14057.0,296350.0,416.0
491
+ 2021-6-18,17262.0,302205.0,453.0
492
+ 2021-6-19,17906.0,308961.0,466.0
493
+ 2021-6-20,17611.0,317493.0,450.0
494
+ 2021-6-21,17378.0,326070.0,440.0
495
+ 2021-6-22,16715.0,331122.0,546.0
496
+ 2021-6-23,17594.0,335508.0,548.0
497
+ 2021-6-24,20182.0,341617.0,568.0
498
+ 2021-6-25,20393.0,347385.0,601.0
499
+ 2021-6-26,21665.0,354084.0,619.0
500
+ 2021-6-27,20538.0,361295.0,599.0
501
+ 2021-6-28,21650.0,369708.0,611.0
502
+ 2021-6-29,20616.0,374975.0,652.0
503
+ 2021-6-30,21042.0,378992.0,669.0
504
+ 2021-7-01,23543.0,384935.0,672.0
505
+ 2021-7-02,23218.0,389277.0,679.0
506
+ 2021-7-03,24439.0,395120.0,697.0
507
+ 2021-7-04,25142.0,404115.0,663.0
508
+ 2021-7-05,24353.0,413274.0,654.0
509
+ 2021-7-06,23378.0,417504.0,737.0
510
+ 2021-7-07,23962.0,420674.0,725.0
511
+ 2021-7-08,24818.0,423422.0,734.0
512
+ 2021-7-09,25766.0,426630.0,726.0
513
+ 2021-7-10,25082.0,433210.0,752.0
514
+ 2021-7-11,25033.0,440112.0,749.0
515
+ 2021-7-12,25140.0,448113.0,710.0
516
+ 2021-7-13,24702.0,452469.0,780.0
517
+ 2021-7-14,23827.0,454241.0,786.0
518
+ 2021-7-15,25293.0,457250.0,791.0
519
+ 2021-7-16,25704.0,460223.0,799.0
520
+ 2021-7-17,25116.0,463115.0,787.0
521
+ 2021-7-18,25018.0,468483.0,764.0
522
+ 2021-7-19,24633.0,473633.0,719.0
523
+ 2021-7-20,23770.0,474401.0,784.0
524
+ 2021-7-21,23704.0,474738.0,783.0
525
+ 2021-7-22,24471.0,475753.0,796.0
526
+ 2021-7-23,23811.0,476222.0,795.0
527
+ 2021-7-24,23947.0,477418.0,799.0
528
+ 2021-7-25,24072.0,482033.0,779.0
529
+ 2021-7-26,23239.0,488345.0,727.0
530
+ 2021-7-27,23032.0,490482.0,779.0
531
+ 2021-7-28,22420.0,491525.0,798.0
532
+ 2021-7-29,23270.0,493162.0,799.0
533
+ 2021-7-30,23564.0,495447.0,794.0
534
+ 2021-7-31,23807.0,498691.0,792.0
535
+ 2021-8-01,22804.0,503435.0,789.0
536
+ 2021-8-02,23508.0,511265.0,785.0
537
+ 2021-8-03,22010.0,513524.0,788.0
538
+ 2021-8-04,22589.0,515227.0,790.0
539
+ 2021-8-05,23120.0,517183.0,794.0
540
+ 2021-8-06,22660.0,518910.0,792.0
541
+ 2021-8-07,22320.0,520952.0,793.0
542
+ 2021-8-08,22866.0,527362.0,787.0
543
+ 2021-8-09,22160.0,534279.0,769.0
544
+ 2021-8-10,21378.0,536136.0,792.0
545
+ 2021-8-11,21571.0,536841.0,799.0
546
+ 2021-8-12,21932.0,537770.0,808.0
547
+ 2021-8-13,22277.0,539864.0,815.0
548
+ 2021-8-14,22144.0,541639.0,819.0
549
+ 2021-8-15,21624.0,546021.0,816.0
550
+ 2021-8-16,20765.0,550379.0,806.0
551
+ 2021-8-17,20958.0,552125.0,805.0
552
+ 2021-8-18,20914.0,551527.0,799.0
553
+ 2021-8-19,21058.0,547777.0,791.0
554
+ 2021-8-20,20992.0,547633.0,785.0
555
+ 2021-8-21,21000.0,547189.0,797.0
556
+ 2021-8-22,20564.0,551577.0,762.0
557
+ 2021-8-23,19454.0,554854.0,776.0
558
+ 2021-8-24,18833.0,554257.0,794.0
559
+ 2021-8-25,19536.0,553330.0,809.0
560
+ 2021-8-26,19630.0,552479.0,820.0
561
+ 2021-8-27,19509.0,551973.0,798.0
562
+ 2021-8-28,19492.0,551255.0,799.0
563
+ 2021-8-29,19286.0,552940.0,797.0
564
+ 2021-8-30,18325.0,556293.0,792.0
565
+ 2021-8-31,17813.0,554687.0,795.0
566
+ 2021-9-01,18368.0,553940.0,790.0
567
+ 2021-9-02,18985.0,553458.0,798.0
568
+ 2021-9-03,18856.0,552825.0,799.0
569
+ 2021-9-04,18780.0,552072.0,796.0
570
+ 2021-9-05,18645.0,554668.0,793.0
571
+ 2021-9-06,17856.0,557458.0,790.0
572
+ 2021-9-07,17425.0,556845.0,795.0
573
+ 2021-9-08,18024.0,555810.0,797.0
574
+ 2021-9-09,18380.0,553757.0,794.0
575
+ 2021-9-10,18341.0,554188.0,789.0
576
+ 2021-9-11,18891.0,554395.0,796.0
577
+ 2021-9-12,18554.0,557664.0,788.0
578
+ 2021-9-13,18178.0,562654.0,719.0
579
+ 2021-9-14,17837.0,563803.0,781.0
580
+ 2021-9-15,18841.0,564813.0,792.0
581
+ 2021-9-16,19594.0,566287.0,794.0
582
+ 2021-9-17,19905.0,568782.0,791.0
583
+ 2021-9-18,20329.0,572065.0,799.0
584
+ 2021-9-19,20174.0,578028.0,793.0
585
+ 2021-9-20,19744.0,585002.0,778.0
586
+ 2021-9-21,19179.0,587932.0,812.0
587
+ 2021-9-22,19706.0,590719.0,817.0
588
+ 2021-9-23,21438.0,594770.0,820.0
589
+ 2021-9-24,21379.0,599493.0,828.0
590
+ 2021-9-25,22041.0,604387.0,822.0
591
+ 2021-9-26,22498.0,612409.0,805.0
592
+ 2021-9-27,22236.0,620353.0,779.0
593
+ 2021-9-28,21559.0,623692.0,852.0
594
+ 2021-9-29,22430.0,626809.0,857.0
595
+ 2021-9-30,23888.0,631004.0,867.0
596
+ 2021-10-01,24522.0,634684.0,887.0
597
+ 2021-10-02,25219.0,641165.0,886.0
598
+ 2021-10-03,25769.0,650653.0,890.0
599
+ 2021-10-04,25781.0,661025.0,883.0
600
+ 2021-10-05,25110.0,666672.0,895.0
601
+ 2021-10-06,25133.0,671035.0,929.0
602
+ 2021-10-07,27550.0,677331.0,924.0
603
+ 2021-10-08,27246.0,683075.0,936.0
604
+ 2021-10-09,29362.0,690420.0,968.0
605
+ 2021-10-10,28647.0,700831.0,962.0
606
+ 2021-10-11,29409.0,713823.0,957.0
607
+ 2021-10-12,28190.0,720334.0,973.0
608
+ 2021-10-13,28717.0,726266.0,984.0
609
+ 2021-10-14,31299.0,734909.0,986.0
610
+ 2021-10-15,32196.0,743839.0,998.0
611
+ 2021-10-16,33208.0,754162.0,1002.0
612
+ 2021-10-17,34303.0,768751.0,997.0
613
+ 2021-10-18,34325.0,785647.0,998.0
614
+ 2021-10-19,33740.0,794946.0,1015.0
615
+ 2021-10-20,34073.0,802760.0,1028.0
616
+ 2021-10-21,36339.0,812168.0,1036.0
617
+ 2021-10-22,37141.0,822792.0,1064.0
618
+ 2021-10-23,37678.0,833318.0,1075.0
619
+ 2021-10-24,35660.0,845122.0,1072.0
620
+ 2021-10-25,37930.0,861293.0,1069.0
621
+ 2021-10-26,36446.0,869660.0,1106.0
622
+ 2021-10-27,36582.0,875968.0,1123.0
623
+ 2021-10-28,40096.0,885587.0,1159.0
624
+ 2021-10-29,39849.0,893811.0,1163.0
625
+ 2021-10-30,40251.0,903993.0,1160.0
626
+ 2021-10-31,40993.0,916713.0,1158.0
627
+ 2021-11-01,40402.0,932773.0,1155.0
628
+ 2021-11-02,39008.0,939698.0,1178.0
629
+ 2021-11-03,40443.0,946145.0,1189.0
630
+ 2021-11-04,40217.0,953239.0,1195.0
631
+ 2021-11-05,40735.0,964177.0,1192.0
632
+ 2021-11-06,41335.0,975123.0,1188.0
633
+ 2021-11-07,39165.0,986303.0,1179.0
634
+ 2021-11-08,39400.0,998931.0,1190.0
635
+ 2021-11-09,39160.0,1004844.0,1211.0
636
+ 2021-11-10,38058.0,1007098.0,1239.0
637
+ 2021-11-11,40759.0,1013464.0,1237.0
638
+ 2021-11-12,40123.0,1018707.0,1235.0
639
+ 2021-11-13,39256.0,1022920.0,1241.0
640
+ 2021-11-14,38823.0,1030703.0,1219.0
641
+ 2021-11-15,38420.0,1040210.0,1211.0
642
+ 2021-11-16,36818.0,1041627.0,1240.0
643
+ 2021-11-17,36626.0,1040618.0,1247.0
644
+ 2021-11-18,37374.0,1040327.0,1251.0
645
+ 2021-11-19,37156.0,1039225.0,1254.0
646
+ 2021-11-20,37120.0,1038919.0,1254.0
647
+ 2021-11-21,36970.0,1042133.0,1252.0
648
+ 2021-11-22,35681.0,1047860.0,1241.0
649
+ 2021-11-23,33996.0,1044562.0,1243.0
650
+ 2021-11-24,33558.0,1040198.0,1240.0
651
+ 2021-11-25,33796.0,1034306.0,1238.0
652
+ 2021-11-26,34690.0,1031616.0,1235.0
653
+ 2021-11-27,33946.0,1027829.0,1239.0
654
+ 2021-11-28,33548.0,1029507.0,1224.0
655
+ 2021-11-29,33860.0,1034458.0,1209.0
656
+ 2021-11-30,32648.0,1032435.0,1229.0
657
+ 2021-12-01,32837.0,1028367.0,1226.0
658
+ 2021-12-02,33389.0,1025350.0,1221.0
659
+ 2021-12-03,32930.0,1020549.0,1217.0
660
+ 2021-12-04,32974.0,1017126.0,1215.0
661
+ 2021-12-05,32602.0,1017929.0,1206.0
662
+ 2021-12-06,32136.0,1020811.0,1184.0
663
+ 2021-12-07,31096.0,1016110.0,1182.0
664
+ 2021-12-08,30752.0,1008707.0,1179.0
665
+ 2021-12-09,30209.0,1001941.0,1181.0
666
+ 2021-12-10,30873.0,995981.0,1176.0
667
+ 2021-12-11,30288.0,988652.0,1171.0
668
+ 2021-12-12,29929.0,986058.0,1132.0
669
+ 2021-12-13,29558.0,985934.0,1121.0
670
+ 2021-12-14,28343.0,979048.0,1145.0
671
+ 2021-12-15,28363.0,970636.0,1142.0
672
+ 2021-12-16,28486.0,960834.0,1133.0
673
+ 2021-12-17,27743.0,950060.0,1080.0
674
+ 2021-12-18,27434.0,938377.0,1076.0
675
+ 2021-12-19,27967.0,932666.0,1023.0
676
+ 2021-12-20,27022.0,928610.0,1019.0
677
+ 2021-12-21,25907.0,913271.0,1027.0
678
+ 2021-12-22,25264.0,895193.0,1020.0
679
+ 2021-12-23,25667.0,878213.0,1002.0
680
+ 2021-12-24,24703.0,860705.0,998.0
681
+ 2021-12-25,24946.0,842563.0,981.0
682
+ 2021-12-26,23721.0,828031.0,968.0
683
+ 2021-12-27,23210.0,816589.0,937.0
684
+ 2021-12-28,21922.0,793615.0,935.0
685
+ 2021-12-29,21119.0,771026.0,932.0
686
+ 2021-12-30,21073.0,748169.0,926.0
687
+ 2021-12-31,20638.0,727203.0,912.0
688
+ 2022-1-01,19751.0,712963.0,847.0
689
+ 2022-1-02,18233.0,703409.0,811.0
690
+ 2022-1-03,16343.0,694880.0,835.0
691
+ 2022-1-04,15903.0,682878.0,834.0
692
+ 2022-1-05,15772.0,672241.0,828.0
693
+ 2022-1-06,15316.0,663806.0,802.0
694
+ 2022-1-07,16735.0,657719.0,787.0
695
+ 2022-1-08,16568.0,653042.0,796.0
696
+ 2022-1-09,16246.0,647774.0,763.0
697
+ 2022-1-10,15830.0,642973.0,741.0
698
+ 2022-1-11,17525.0,634499.0,783.0
699
+ 2022-1-12,17946.0,625354.0,745.0
700
+ 2022-1-13,21155.0,619785.0,740.0
701
+ 2022-1-14,23820.0,617914.0,739.0
702
+ 2022-1-15,27179.0,617786.0,723.0
703
+ 2022-1-16,29230.0,623599.0,686.0
704
+ 2022-1-17,30726.0,633899.0,670.0
705
+ 2022-1-18,31252.0,639899.0,688.0
706
+ 2022-1-19,33899.0,650180.0,698.0
707
+ 2022-1-20,38850.0,663868.0,684.0
708
+ 2022-1-21,49513.0,687970.0,692.0
709
+ 2022-1-22,57212.0,718976.0,681.0
710
+ 2022-1-23,63205.0,758457.0,679.0
711
+ 2022-1-24,65109.0,801197.0,655.0
712
+ 2022-1-25,67809.0,841921.0,681.0
713
+ 2022-1-26,74692.0,887759.0,657.0
714
+ 2022-1-27,88816.0,946156.0,665.0
715
+ 2022-1-28,98040.0,1014017.0,673.0
716
+ 2022-1-29,113122.0,1096461.0,668.0
717
+ 2022-1-30,121228.0,1188128.0,617.0
718
+ 2022-1-31,124070.0,1281447.0,621.0
719
+ 2022-2-01,125836.0,1366319.0,663.0
720
+ 2022-2-02,141883.0,1459098.0,678.0
721
+ 2022-2-03,155768.0,1560475.0,667.0
722
+ 2022-2-04,168201.0,1669545.0,682.0
723
+ 2022-2-05,177282.0,1785606.0,714.0
724
+ 2022-2-06,180071.0,1905433.0,661.0
725
+ 2022-2-07,171905.0,2021046.0,609.0
726
+ 2022-2-08,165643.0,2104803.0,698.0
727
+ 2022-2-09,183103.0,2190074.0,669.0
728
+ 2022-2-10,197076.0,2280357.0,701.0
729
+ 2022-2-11,203949.0,2371348.0,722.0
730
+ 2022-2-12,203766.0,2461727.0,729.0
731
+ 2022-2-13,197949.0,2557402.0,706.0
732
+ 2022-2-14,180456.0,2639990.0,683.0
733
+ 2022-2-15,166631.0,2668036.0,704.0
734
+ 2022-2-16,179284.0,2674104.0,748.0
735
+ 2022-2-17,180622.0,2668854.0,790.0
736
+ 2022-2-18,180071.0,2649772.0,784.0
737
+ 2022-2-19,179147.0,2637023.0,798.0
738
+ 2022-2-20,170699.0,2659681.0,745.0
739
+ 2022-2-21,152337.0,2683789.0,735.0
740
+ 2022-2-22,135172.0,2653872.0,796.0
741
+ 2022-2-23,137642.0,2611526.0,785.0
742
+ 2022-2-24,132998.0,2607329.0,762.0
743
+ 2022-2-25,123460.0,2562692.0,787.0
744
+ 2022-2-26,122995.0,2503551.0,793.0
745
+ 2022-2-27,116093.0,2481279.0,769.0
746
+ 2022-2-28,106920.0,2470724.0,733.0
747
+ 2022-3-01,97333.0,2394458.0,786.0
748
+ 2022-3-02,97455.0,2307599.0,784.0
749
+ 2022-3-03,93026.0,2229155.0,781.0
750
+ 2022-3-04,89174.0,2157046.0,776.0
751
+ 2022-3-05,86769.0,2087340.0,750.0
752
+ 2022-3-06,79863.0,2017399.0,744.0
753
+ 2022-3-07,73162.0,1967274.0,668.0
754
+ 2022-3-08,66576.0,1921442.0,652.0
755
+ 2022-3-09,58675.0,1886740.0,645.0
756
+ 2022-3-10,51231.0,1788672.0,665.0
757
+ 2022-3-11,50743.0,1687531.0,674.0
758
+ 2022-3-12,48154.0,1578204.0,630.0
759
+ 2022-3-13,44989.0,1493968.0,596.0
760
+ 2022-3-14,41055.0,1429247.0,533.0
761
+ 2022-3-15,36678.0,1346995.0,558.0
762
+ 2022-3-16,36519.0,1264929.0,576.0
763
+ 2022-3-17,34819.0,1186573.0,561.0
764
+ 2022-3-18,34442.0,1115427.0,524.0
765
+ 2022-3-19,32958.0,1052332.0,495.0
766
+ 2022-3-20,31035.0,1008258.0,434.0
767
+ 2022-3-21,28709.0,974620.0,409.0
768
+ 2022-3-22,26394.0,925622.0,472.0
769
+ 2022-3-23,26826.0,881397.0,429.0
770
+ 2022-3-24,25387.0,839890.0,418.0
771
+ 2022-3-25,25382.0,803014.0,398.0
772
+ 2022-3-26,24072.0,765758.0,395.0
773
+ 2022-3-27,23280.0,741160.0,338.0
774
+ 2022-3-28,21101.0,726180.0,335.0
775
+ 2022-3-29,19660.0,698272.0,339.0
776
+ 2022-3-30,20145.0,666980.0,352.0
777
+ 2022-3-31,19277.0,635416.0,345.0
778
+ 2022-4-01,19164.0,608240.0,342.0
779
+ 2022-4-02,17949.0,581302.0,340.0
780
+ 2022-4-03,16828.0,562131.0,304.0
781
+ 2022-4-04,15291.0,544285.0,287.0
782
+ 2022-4-05,13947.0,520457.0,316.0
783
+ 2022-4-06,14661.0,499521.0,291.0
784
+ 2022-4-07,14355.0,478285.0,287.0
785
+ 2022-4-08,14311.0,454632.0,280.0
786
+ 2022-4-09,13573.0,427817.0,288.0
787
+ 2022-4-10,13056.0,411242.0,259.0
788
+ 2022-4-11,11855.0,396094.0,248.0
789
+ 2022-4-12,10910.0,375452.0,281.0
790
+ 2022-4-13,11754.0,358804.0,267.0
791
+ 2022-4-14,11348.0,348045.0,254.0
792
+ 2022-4-15,11432.0,339693.0,261.0
793
+ 2022-4-16,11095.0,331542.0,240.0
794
+ 2022-4-17,10263.0,325076.0,233.0
795
+ 2022-4-18,9434.0,319973.0,213.0
796
+ 2022-4-19,8640.0,311615.0,235.0
797
+ 2022-4-20,9195.0,304387.0,223.0
798
+ 2022-4-21,8875.0,298883.0,197.0
799
+ 2022-4-22,9001.0,294209.0,195.0
800
+ 2022-4-23,8829.0,289837.0,171.0
801
+ 2022-4-24,8446.0,287607.0,168.0
802
+ 2022-4-25,7651.0,286244.0,159.0
803
+ 2022-4-26,7107.0,281276.0,176.0
804
+ 2022-4-27,7705.0,277341.0,163.0
805
+ 2022-4-28,7681.0,273793.0,166.0
806
+ 2022-4-29,7710.0,270301.0,161.0
807
+ 2022-4-30,7363.0,266485.0,157.0
808
+ 2022-5-01,7047.0,264652.0,147.0
809
+ 2022-5-02,6207.0,263221.0,136.0
810
+ 2022-5-03,5466.0,261510.0,125.0
811
+ 2022-5-04,5093.0,259810.0,129.0
812
+ 2022-5-05,5011.0,255959.0,139.0
813
+ 2022-5-06,5541.0,251956.0,136.0
814
+ 2022-5-07,5500.0,248675.0,132.0
815
+ 2022-5-08,5447.0,247322.0,118.0
816
+ 2022-5-09,5030.0,246290.0,103.0
817
+ 2022-5-10,4531.0,245706.0,101.0
818
+ 2022-5-11,4102.0,244667.0,98.0
819
+ 2022-5-12,4065.0,241691.0,111.0
820
+ 2022-5-13,4896.0,239225.0,105.0
821
+ 2022-5-14,5047.0,236787.0,107.0
src/lab2_functions.py ADDED
@@ -0,0 +1,626 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Функции для лабораторной работы №2: Прогнозирование временных рядов
3
+ """
4
+ import numpy as np
5
+ import pandas as pd
6
+ from typing import List, Tuple, Dict, Optional
7
+ from scipy import stats
8
+ from scipy.stats import boxcox, boxcox_normmax
9
+ from statsmodels.tsa.holtwinters import ExponentialSmoothing
10
+ from statsmodels.stats.diagnostic import acorr_ljungbox
11
+ from statsmodels.tsa.stattools import adfuller, kpss
12
+ from sklearn.model_selection import TimeSeriesSplit
13
+ from sklearn.metrics import mean_absolute_error, mean_squared_error
14
+ import warnings
15
+ warnings.filterwarnings('ignore')
16
+
17
+
18
+ def calculate_mape(y_true: np.ndarray, y_pred: np.ndarray) -> float:
19
+ """Вычисляет MAPE (Mean Absolute Percentage Error)"""
20
+ y_true = np.array(y_true)
21
+ y_pred = np.array(y_pred)
22
+ mask = y_true != 0
23
+ if mask.sum() == 0:
24
+ return np.nan
25
+ return np.mean(np.abs((y_true[mask] - y_pred[mask]) / y_true[mask])) * 100
26
+
27
+
28
+ def create_advanced_features(df: pd.DataFrame, target: str, timestamp_col: str = 'timestamp') -> pd.DataFrame:
29
+ """
30
+ Расширенный feature engineering:
31
+ - Временные признаки (день недели, месяц, квартал)
32
+ - Циклические признаки через sin/cos
33
+ - Лаги: lag_1, lag_7, lag_30
34
+ - Скользящие статистики: mean, std, min, max по окнам 7, 30, 90
35
+ """
36
+ df = df.copy()
37
+ df = df.set_index(timestamp_col).sort_index()
38
+
39
+ # Временные признаки
40
+ df['day_of_week'] = df.index.dayofweek
41
+ df['month'] = df.index.month
42
+ df['quarter'] = df.index.quarter
43
+ df['day_of_month'] = df.index.day
44
+ df['week_of_year'] = df.index.isocalendar().week
45
+
46
+ # Циклические признаки
47
+ df['day_of_week_sin'] = np.sin(2 * np.pi * df['day_of_week'] / 7)
48
+ df['day_of_week_cos'] = np.cos(2 * np.pi * df['day_of_week'] / 7)
49
+ df['month_sin'] = np.sin(2 * np.pi * df['month'] / 12)
50
+ df['month_cos'] = np.cos(2 * np.pi * df['month'] / 12)
51
+
52
+ # Лаги
53
+ for lag in [1, 7, 30]:
54
+ df[f'{target}_lag_{lag}'] = df[target].shift(lag)
55
+
56
+ # Скользящие статистики
57
+ windows = [7, 30, 90]
58
+ for w in windows:
59
+ df[f'{target}_rolling_mean_{w}'] = df[target].rolling(window=w, min_periods=1).mean()
60
+ df[f'{target}_rolling_std_{w}'] = df[target].rolling(window=w, min_periods=1).std()
61
+ df[f'{target}_rolling_min_{w}'] = df[target].rolling(window=w, min_periods=1).min()
62
+ df[f'{target}_rolling_max_{w}'] = df[target].rolling(window=w, min_periods=1).max()
63
+
64
+ # Коэффициент вариации (волатильность)
65
+ for w in [7, 30]:
66
+ rolling_mean = df[f'{target}_rolling_mean_{w}']
67
+ rolling_std = df[f'{target}_rolling_std_{w}']
68
+ df[f'{target}_rolling_cv_{w}'] = rolling_std / (rolling_mean + 1e-8)
69
+
70
+ return df.reset_index()
71
+
72
+
73
+ def apply_boxcox_transform(series: pd.Series, lambda_param: Optional[float] = None) -> Tuple[pd.Series, float]:
74
+ """
75
+ Применяет преобразование Бокса-Кокса.
76
+ Если lambda_param не указан, подбирает оптимальный.
77
+ """
78
+ series_positive = series[series > 0]
79
+ if len(series_positive) == 0:
80
+ raise ValueError("Все значения должны быть положительными для преобразования Бокса-Кокса")
81
+
82
+ if lambda_param is None:
83
+ # Автоматический подбор lambda
84
+ lambda_param = boxcox_normmax(series_positive.values)
85
+
86
+ transformed_values, fitted_lambda = boxcox(series_positive.values, lmbda=lambda_param)
87
+
88
+ # Создаём новый Series с теми же индексами
89
+ result = pd.Series(index=series.index, dtype=float)
90
+ result.loc[series > 0] = transformed_values
91
+
92
+ return result, fitted_lambda
93
+
94
+
95
+ def inverse_boxcox_transform(transformed_series: pd.Series, lambda_param: float) -> pd.Series:
96
+ """Обратное преобразование Бокса-Кокса"""
97
+ if lambda_param == 0:
98
+ return np.exp(transformed_series)
99
+ else:
100
+ return (lambda_param * transformed_series + 1) ** (1 / lambda_param)
101
+
102
+
103
+ def inverse_transformations(
104
+ forecast: np.ndarray,
105
+ last_train_values_transformed: np.ndarray,
106
+ transform_info: Dict
107
+ ) -> np.ndarray:
108
+ """
109
+ Применяет обратные преобразования к прогнозу.
110
+
111
+ Порядок обратного преобразования должен быть обратным порядку прямого:
112
+ Прямое: transformation -> diff_order -> seasonal_diff
113
+ Обратное: seasonal_diff -> diff_order -> transformation
114
+
115
+ forecast: прогноз в преобразованном пространстве (после всех преобразований)
116
+ last_train_values_transformed: последние значения обучающей выборки в преобразованном пространстве (после всех преобразований)
117
+ transform_info: информация о применённых преобразованиях (может содержать промежуточные значения)
118
+ """
119
+ result = forecast.copy()
120
+ diff_order = transform_info.get('diff_order', 0)
121
+ seasonal_diff = transform_info.get('seasonal_diff')
122
+
123
+ # Получаем промежуточные значения из transform_info, если они есть
124
+ last_values_after_diff = transform_info.get('last_values_after_diff', None)
125
+ last_values_after_transform = transform_info.get('last_values_after_transform', None)
126
+
127
+ # 1. Обратное сезонное дифференцирование (если было)
128
+ if seasonal_diff is not None and seasonal_diff > 0:
129
+ # Нужны последние seasonal_diff значений после transformation и diff, но до seasonal_diff
130
+ if last_values_after_diff is not None and len(last_values_after_diff) >= seasonal_diff:
131
+ last_seasonal = last_values_after_diff[-seasonal_diff:]
132
+ elif len(last_train_values_transformed) >= seasonal_diff:
133
+ # Fallback: используем последние значения (хотя это не совсем правильно)
134
+ last_seasonal = last_train_values_transformed[-seasonal_diff:]
135
+ else:
136
+ last_seasonal = last_train_values_transformed if len(last_train_values_transformed) > 0 else np.array([0])
137
+
138
+ for i in range(len(result)):
139
+ if i < len(last_seasonal):
140
+ result[i] = result[i] + last_seasonal[i]
141
+ else:
142
+ # Используем предыдущие прогнозы
143
+ result[i] = result[i] + result[i - seasonal_diff]
144
+
145
+ # 2. Обратное обычное дифференцирование (если было)
146
+ for _ in range(diff_order):
147
+ # Нужны последние diff_order значений после transformation, но до diff
148
+ if last_values_after_transform is not None and len(last_values_after_transform) > 0:
149
+ last_val = last_values_after_transform[-1]
150
+ elif len(last_train_values_transformed) > 0:
151
+ # Fallback
152
+ last_val = last_train_values_transformed[-1]
153
+ else:
154
+ last_val = 0
155
+
156
+ for i in range(len(result)):
157
+ if i == 0:
158
+ result[i] = result[i] + last_val
159
+ else:
160
+ result[i] = result[i] + result[i - 1]
161
+
162
+ # 3. Обратное преобразование для стабилизации дисперсии
163
+ if transform_info.get('transformation') == 'log':
164
+ result = np.exp(result)
165
+ elif transform_info.get('transformation') == 'boxcox':
166
+ lambda_param = transform_info.get('lambda')
167
+ if lambda_param is not None:
168
+ if lambda_param == 0:
169
+ result = np.exp(result)
170
+ else:
171
+ result = (lambda_param * result + 1) ** (1 / lambda_param)
172
+
173
+ return result
174
+
175
+
176
+ def apply_transformations(
177
+ series: pd.Series,
178
+ transformation: str = 'none',
179
+ lambda_param: Optional[float] = None,
180
+ diff_order: int = 0,
181
+ seasonal_diff: Optional[int] = None
182
+ ) -> Tuple[pd.Series, Dict]:
183
+ """
184
+ Применяет цепочку преобразований к ряду.
185
+
186
+ transformation: 'none', 'log', 'boxcox'
187
+ diff_order: порядок обычного дифференцирования
188
+ seasonal_diff: период сезонного дифференцирования
189
+
190
+ Возвращает преобразованный ряд и словарь с информацией о преобразованиях,
191
+ включая промежуточные значения для обратного преобразования.
192
+ """
193
+ result = series.copy()
194
+ info = {'transformation': transformation, 'lambda': None, 'diff_order': diff_order, 'seasonal_diff': seasonal_diff}
195
+
196
+ # Преобразование для стабилизации дисперсии
197
+ if transformation == 'log':
198
+ if (result <= 0).any():
199
+ raise ValueError("Для лог-трансформации все значения должны быть положительными")
200
+ result = np.log(result)
201
+ elif transformation == 'boxcox':
202
+ result, lambda_param = apply_boxcox_transform(result, lambda_param)
203
+ info['lambda'] = lambda_param
204
+
205
+ # Сохраняем значения после transformation (для обратного diff)
206
+ result_after_transform = result.copy()
207
+
208
+ # Обычное дифференцирование
209
+ for _ in range(diff_order):
210
+ result = result.diff()
211
+
212
+ # Сохраняем значения после diff (для обратного seasonal_diff)
213
+ result_after_diff = result.copy()
214
+
215
+ # Сезонное дифференцирование
216
+ if seasonal_diff is not None and seasonal_diff > 0:
217
+ result = result.diff(periods=seasonal_diff)
218
+
219
+ # Сохраняем промежуточные значения для обратного преобразования
220
+ info['last_values_after_transform'] = result_after_transform.values[-max(diff_order, 1):] if len(result_after_transform) > 0 else np.array([])
221
+ info['last_values_after_diff'] = result_after_diff.values[-max(seasonal_diff if seasonal_diff else 1, 1):] if len(result_after_diff) > 0 else np.array([])
222
+
223
+ return result.dropna(), info
224
+
225
+
226
+ def recursive_forecast(
227
+ model_func,
228
+ train_data: pd.Series,
229
+ horizon: int,
230
+ alpha: Optional[float] = None,
231
+ **model_kwargs
232
+ ) -> Tuple[np.ndarray, Optional[Tuple[np.ndarray, np.ndarray]]]:
233
+ """
234
+ Рекурсивная стратегия прогнозирования:
235
+ Одна модель → итеративное использование прогнозов
236
+
237
+ Возвращает прогнозы и опционально доверительные интервалы (lower, upper)
238
+ """
239
+ forecasts = []
240
+ conf_lower = [] if alpha is not None else None
241
+ conf_upper = [] if alpha is not None else None
242
+ current_data = train_data.copy()
243
+
244
+ # Определяем тип индекса для правильного добавления новых значений
245
+ is_datetime = pd.api.types.is_datetime64_any_dtype(current_data.index)
246
+
247
+ for h in range(horizon):
248
+ # Обучаем модель на текущих данных
249
+ model = model_func(current_data, **model_kwargs)
250
+ # Прогнозируем на 1 шаг вперёд
251
+ if alpha is not None:
252
+ try:
253
+ forecast_result = model.forecast(steps=1, alpha=alpha)
254
+ if isinstance(forecast_result, tuple):
255
+ forecast_value = forecast_result[0][0] if len(forecast_result[0]) > 0 else forecast_result[0]
256
+ if len(forecast_result) > 1:
257
+ conf_lower.append(forecast_result[1][0] if len(forecast_result[1]) > 0 else forecast_result[1])
258
+ conf_upper.append(forecast_result[2][0] if len(forecast_result[2]) > 0 else forecast_result[2])
259
+ else:
260
+ forecast_value = forecast_result[0] if hasattr(forecast_result, '__getitem__') else float(forecast_result)
261
+ except:
262
+ # Если доверительные интервалы не поддерживаются, используем обычный прогноз
263
+ forecast = model.forecast(steps=1)
264
+ forecast_value = forecast[0] if hasattr(forecast, '__getitem__') else float(forecast)
265
+ else:
266
+ forecast = model.forecast(steps=1)
267
+ forecast_value = forecast[0] if hasattr(forecast, '__getitem__') else float(forecast)
268
+
269
+ forecasts.append(forecast_value)
270
+
271
+ # Добавляем прогноз к данным для следующей итерации
272
+ if is_datetime:
273
+ # Для DatetimeIndex используем частоту или инференс
274
+ try:
275
+ freq = pd.infer_freq(current_data.index) or 'D'
276
+ # Используем pd.date_range для создания следующей даты
277
+ last_date = current_data.index[-1]
278
+ next_dates = pd.date_range(start=last_date, periods=2, freq=freq)
279
+ if len(next_dates) >= 2:
280
+ next_idx = next_dates[1] # Берём вторую дату (первая = last_date)
281
+ else:
282
+ # Fallback
283
+ next_idx = len(current_data)
284
+ is_datetime = False
285
+ except:
286
+ # Если не удалось определить частоту, используем числовой индекс
287
+ try:
288
+ # Пробуем простой способ через Timedelta
289
+ next_idx = current_data.index[-1] + pd.Timedelta(days=1)
290
+ except:
291
+ next_idx = len(current_data)
292
+ is_datetime = False
293
+ else:
294
+ # Для числового индекса просто увеличиваем на 1
295
+ next_idx = len(current_data)
296
+
297
+ if is_datetime:
298
+ current_data = pd.concat([current_data, pd.Series([forecast_value], index=[next_idx])])
299
+ else:
300
+ # Используем числовой индекс
301
+ current_data = pd.concat([current_data, pd.Series([forecast_value], index=[next_idx])])
302
+
303
+ result = np.array(forecasts)
304
+ if alpha is not None and conf_lower and conf_upper:
305
+ return result, (np.array(conf_lower), np.array(conf_upper))
306
+ return result, None
307
+
308
+
309
+ def direct_forecast(
310
+ model_func,
311
+ train_data: pd.Series,
312
+ horizon: int,
313
+ alpha: Optional[float] = None,
314
+ **model_kwargs
315
+ ) -> Tuple[np.ndarray, Optional[Tuple[np.ndarray, np.ndarray]]]:
316
+ """
317
+ Прямая стратегия прогнозирования:
318
+ Отдельная модель для каждого шага t+1, ..., t+h
319
+
320
+ Возвращает прогнозы и опционально доверительные интервалы (lower, upper)
321
+ """
322
+ forecasts = []
323
+ conf_lower = [] if alpha is not None else None
324
+ conf_upper = [] if alpha is not None else None
325
+
326
+ for h in range(1, horizon + 1):
327
+ # Обучаем отдельную модель для шага h
328
+ model = model_func(train_data, **model_kwargs)
329
+ # Прогнозируем на h шагов вперёд и берём последний
330
+ if alpha is not None:
331
+ try:
332
+ forecast_result = model.forecast(steps=h, alpha=alpha)
333
+ if isinstance(forecast_result, tuple):
334
+ forecast_value = forecast_result[0][-1] if len(forecast_result[0]) > 0 else forecast_result[0]
335
+ if len(forecast_result) > 1:
336
+ conf_lower.append(forecast_result[1][-1] if len(forecast_result[1]) > 0 else forecast_result[1])
337
+ conf_upper.append(forecast_result[2][-1] if len(forecast_result[2]) > 0 else forecast_result[2])
338
+ else:
339
+ forecast_value = forecast_result[-1]
340
+ except:
341
+ forecast = model.forecast(steps=h)
342
+ forecast_value = forecast[-1]
343
+ else:
344
+ forecast = model.forecast(steps=h)
345
+ forecast_value = forecast[-1]
346
+
347
+ forecasts.append(forecast_value)
348
+
349
+ result = np.array(forecasts)
350
+ if alpha is not None and conf_lower and conf_upper:
351
+ return result, (np.array(conf_lower), np.array(conf_upper))
352
+ return result, None
353
+
354
+
355
+ def hybrid_forecast(
356
+ model_func,
357
+ train_data: pd.Series,
358
+ horizon: int,
359
+ recursive_steps: int = None,
360
+ alpha: Optional[float] = None,
361
+ **model_kwargs
362
+ ) -> Tuple[np.ndarray, Optional[Tuple[np.ndarray, np.ndarray]]]:
363
+ """
364
+ Гибридная стратегия:
365
+ Рекурсивная для ближайших шагов, прямая — для дальних
366
+
367
+ Возвращает прогнозы и опционально доверительные интервалы (lower, upper)
368
+ """
369
+ if recursive_steps is None:
370
+ recursive_steps = max(1, horizon // 2)
371
+
372
+ forecasts = []
373
+ conf_lower = [] if alpha is not None else None
374
+ conf_upper = [] if alpha is not None else None
375
+
376
+ # Рекурсивная часть
377
+ recursive_result = recursive_forecast(model_func, train_data, recursive_steps, alpha=alpha, **model_kwargs)
378
+ if isinstance(recursive_result, tuple):
379
+ recursive_forecasts, recursive_conf = recursive_result
380
+ if recursive_conf is not None:
381
+ conf_lower.extend(recursive_conf[0])
382
+ conf_upper.extend(recursive_conf[1])
383
+ else:
384
+ recursive_forecasts = recursive_result
385
+ forecasts.extend(recursive_forecasts)
386
+
387
+ # Прямая часть для оставшихся шагов
388
+ if horizon > recursive_steps:
389
+ # Используем последние данные + рекурсивные прогнозы
390
+ is_datetime = pd.api.types.is_datetime64_any_dtype(train_data.index)
391
+
392
+ if is_datetime:
393
+ try:
394
+ freq = pd.infer_freq(train_data.index) or 'D'
395
+ # Используем pd.date_range для создания дат начиная с последней даты + 1 период
396
+ last_date = train_data.index[-1]
397
+ extended_index = pd.date_range(
398
+ start=last_date,
399
+ periods=len(recursive_forecasts) + 1,
400
+ freq=freq
401
+ )[1:] # Берём все даты кроме первой (которая равна last_date)
402
+ except:
403
+ # Fallback на числовой индекс
404
+ try:
405
+ # Пробуем через date_range с periods
406
+ last_date = train_data.index[-1]
407
+ extended_index = pd.date_range(
408
+ start=last_date,
409
+ periods=len(recursive_forecasts) + 1,
410
+ freq='D'
411
+ )[1:] # Берём все даты кроме первой
412
+ except:
413
+ extended_index = range(len(train_data), len(train_data) + len(recursive_forecasts))
414
+ else:
415
+ extended_index = range(len(train_data), len(train_data) + len(recursive_forecasts))
416
+
417
+ extended_data = pd.concat([
418
+ train_data,
419
+ pd.Series(recursive_forecasts, index=extended_index)
420
+ ])
421
+
422
+ remaining_horizon = horizon - recursive_steps
423
+ direct_result = direct_forecast(model_func, extended_data, remaining_horizon, alpha=alpha, **model_kwargs)
424
+ if isinstance(direct_result, tuple):
425
+ direct_forecasts, direct_conf = direct_result
426
+ if direct_conf is not None:
427
+ conf_lower.extend(direct_conf[0])
428
+ conf_upper.extend(direct_conf[1])
429
+ else:
430
+ direct_forecasts = direct_result
431
+ forecasts.extend(direct_forecasts)
432
+
433
+ result = np.array(forecasts[:horizon])
434
+ if alpha is not None and conf_lower and conf_upper:
435
+ return result, (np.array(conf_lower[:horizon]), np.array(conf_upper[:horizon]))
436
+ return result, None
437
+
438
+
439
+ def create_exponential_smoothing_model(
440
+ train_data: pd.Series,
441
+ trend: Optional[str] = None,
442
+ seasonal: Optional[str] = None,
443
+ seasonal_periods: Optional[int] = None,
444
+ optimized: bool = True
445
+ ) -> ExponentialSmoothing:
446
+ """Создаёт и обучает модель экспоненциального сглаживания"""
447
+ try:
448
+ model = ExponentialSmoothing(
449
+ train_data,
450
+ trend=trend,
451
+ seasonal=seasonal,
452
+ seasonal_periods=seasonal_periods,
453
+ initialization_method='estimated' if optimized else 'simple'
454
+ )
455
+ fitted_model = model.fit(optimized=optimized)
456
+ return fitted_model
457
+ except Exception as e:
458
+ raise ValueError(f"Ошибка при создании модели: {e}")
459
+
460
+
461
+ def evaluate_forecast(y_true: np.ndarray, y_pred: np.ndarray) -> Dict[str, float]:
462
+ """Вычисляет метрики качества прогноза"""
463
+ y_true = np.array(y_true)
464
+ y_pred = np.array(y_pred)
465
+
466
+ mae = mean_absolute_error(y_true, y_pred)
467
+ rmse = np.sqrt(mean_squared_error(y_true, y_pred))
468
+ mape = calculate_mape(y_true, y_pred)
469
+
470
+ return {
471
+ 'MAE': mae,
472
+ 'RMSE': rmse,
473
+ 'MAPE': mape
474
+ }
475
+
476
+
477
+ def naive_forecast(train_data: pd.Series, horizon: int) -> np.ndarray:
478
+ """Наивный прогноз: y[t+h] = y[t]"""
479
+ last_value = train_data.iloc[-1]
480
+ return np.full(horizon, last_value)
481
+
482
+
483
+ def time_series_cv_sliding_window(
484
+ model_func,
485
+ data: pd.Series,
486
+ train_size: int,
487
+ test_size: int,
488
+ horizon: int,
489
+ step: int = 1,
490
+ **model_kwargs
491
+ ) -> List[Dict]:
492
+ """
493
+ Кросс-валидация со скользящим окном (фиксированная длина обучения)
494
+ """
495
+ results = []
496
+ n = len(data)
497
+
498
+ for i in range(0, n - train_size - test_size + 1, step):
499
+ train_end = i + train_size
500
+ test_end = min(train_end + test_size, n)
501
+
502
+ train_data = data.iloc[i:train_end]
503
+ test_data = data.iloc[train_end:test_end]
504
+
505
+ try:
506
+ model = model_func(train_data, **model_kwargs)
507
+ forecast = model.forecast(steps=min(horizon, len(test_data)))
508
+
509
+ metrics = evaluate_forecast(test_data.values[:len(forecast)], forecast)
510
+ metrics['fold'] = len(results) + 1
511
+ metrics['train_start'] = train_data.index[0]
512
+ metrics['train_end'] = train_data.index[-1]
513
+ metrics['test_start'] = test_data.index[0]
514
+ metrics['test_end'] = test_data.index[-1]
515
+ results.append(metrics)
516
+ except Exception as e:
517
+ print(f"Ошибка в фолде {len(results) + 1}: {e}")
518
+
519
+ return results
520
+
521
+
522
+ def time_series_cv_expanding_window(
523
+ model_func,
524
+ data: pd.Series,
525
+ initial_train_size: int,
526
+ test_size: int,
527
+ horizon: int,
528
+ step: int = 1,
529
+ **model_kwargs
530
+ ) -> List[Dict]:
531
+ """
532
+ Кросс-валидация с расширяющимся окном (обучение растёт со временем)
533
+ """
534
+ results = []
535
+ n = len(data)
536
+
537
+ for i in range(initial_train_size, n - test_size + 1, step):
538
+ train_end = i
539
+ test_end = min(train_end + test_size, n)
540
+
541
+ train_data = data.iloc[:train_end]
542
+ test_data = data.iloc[train_end:test_end]
543
+
544
+ try:
545
+ model = model_func(train_data, **model_kwargs)
546
+ forecast = model.forecast(steps=min(horizon, len(test_data)))
547
+
548
+ metrics = evaluate_forecast(test_data.values[:len(forecast)], forecast)
549
+ metrics['fold'] = len(results) + 1
550
+ metrics['train_start'] = train_data.index[0]
551
+ metrics['train_end'] = train_data.index[-1]
552
+ metrics['test_start'] = test_data.index[0]
553
+ metrics['test_end'] = test_data.index[-1]
554
+ results.append(metrics)
555
+ except Exception as e:
556
+ print(f"Ошибка в фолде {len(results) + 1}: {e}")
557
+
558
+ return results
559
+
560
+
561
+ def diagnose_model_residuals(residuals: np.ndarray, lags: int = 10) -> Dict:
562
+ """
563
+ Диагностика остатк��в модели:
564
+ - Тест Льюнга-Бокса на автокорреляцию
565
+ - Проверка нормальности (Shapiro-Wilk)
566
+ - Q-Q plot данные
567
+ """
568
+ residuals_clean = residuals[~np.isnan(residuals)]
569
+
570
+ if len(residuals_clean) < 3:
571
+ return {'error': 'Недостаточно данных для диагностики'}
572
+
573
+ results = {}
574
+
575
+ # Тест Льюнга-Бокса
576
+ try:
577
+ lb_stat, lb_pvalue = acorr_ljungbox(residuals_clean, lags=min(lags, len(residuals_clean) - 1), return_df=False)
578
+ results['ljung_box'] = {
579
+ 'statistic': float(lb_stat[-1]) if len(lb_stat) > 0 else None,
580
+ 'pvalue': float(lb_pvalue[-1]) if len(lb_pvalue) > 0 else None,
581
+ 'lags': lags
582
+ }
583
+ except Exception as e:
584
+ results['ljung_box'] = {'error': str(e)}
585
+
586
+ # Тест Шапиро-Уилка на нормальность
587
+ try:
588
+ if len(residuals_clean) <= 5000: # Ограничение для Shapiro-Wilk
589
+ shapiro_stat, shapiro_pvalue = stats.shapiro(residuals_clean)
590
+ results['shapiro_wilk'] = {
591
+ 'statistic': float(shapiro_stat),
592
+ 'pvalue': float(shapiro_pvalue)
593
+ }
594
+ else:
595
+ # Для больших выборок используем тест нормальности из scipy
596
+ k2_stat, k2_pvalue = stats.normaltest(residuals_clean)
597
+ results['normality_test'] = {
598
+ 'statistic': float(k2_stat),
599
+ 'pvalue': float(k2_pvalue),
600
+ 'test': 'normaltest'
601
+ }
602
+ except Exception as e:
603
+ results['normality_test'] = {'error': str(e)}
604
+
605
+ # Статистики остатков
606
+ results['residual_stats'] = {
607
+ 'mean': float(np.mean(residuals_clean)),
608
+ 'std': float(np.std(residuals_clean)),
609
+ 'min': float(np.min(residuals_clean)),
610
+ 'max': float(np.max(residuals_clean)),
611
+ 'count': len(residuals_clean)
612
+ }
613
+
614
+ # Проверка стационарности остатков
615
+ try:
616
+ adf_stat, adf_pvalue, _, _, _, _ = adfuller(residuals_clean)
617
+ kpss_stat, kpss_pvalue, _, _ = kpss(residuals_clean)
618
+ results['stationarity'] = {
619
+ 'adf': {'statistic': float(adf_stat), 'pvalue': float(adf_pvalue)},
620
+ 'kpss': {'statistic': float(kpss_stat), 'pvalue': float(kpss_pvalue)}
621
+ }
622
+ except Exception as e:
623
+ results['stationarity'] = {'error': str(e)}
624
+
625
+ return results
626
+
src/lab3_functions.py ADDED
@@ -0,0 +1,192 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # TimeSeriesHomework/src/lab3_functions.py
2
+ """
3
+ Набор вспомогательных функций для ЛР3:
4
+ - обёртки для SARIMAX, auto_arima (pmdarima), VAR, GARCH (arch) и т.п.
5
+ - forecast helpers
6
+ - простые метрики
7
+ Файл не использует абсолютных путей и предназначен для импорта в проекте.
8
+ """
9
+
10
+ import warnings
11
+ warnings.filterwarnings("ignore")
12
+
13
+ from typing import Tuple, Dict, Any, Optional, List
14
+ import numpy as np
15
+ import pandas as pd
16
+
17
+ # optional heavy deps
18
+ try:
19
+ import pmdarima as pm
20
+ PM_AVAILABLE = True
21
+ except Exception:
22
+ PM_AVAILABLE = False
23
+
24
+ try:
25
+ from statsmodels.tsa.statespace.sarimax import SARIMAX
26
+ from statsmodels.tsa.api import VAR
27
+ STATSMODELS_AVAILABLE = True
28
+ except Exception:
29
+ STATSMODELS_AVAILABLE = False
30
+
31
+ try:
32
+ from arch import arch_model
33
+ ARCH_AVAILABLE = True
34
+ except Exception:
35
+ ARCH_AVAILABLE = False
36
+
37
+ # sklearn metrics used for convenience (optional)
38
+ try:
39
+ from sklearn.metrics import mean_absolute_error, mean_squared_error
40
+ SKLEARN_AVAILABLE = True
41
+ except Exception:
42
+ SKLEARN_AVAILABLE = False
43
+
44
+
45
+ def is_pandas_series(x: Any) -> bool:
46
+ return isinstance(x, (pd.Series,))
47
+
48
+
49
+ def mae_rmse(y_true, y_pred) -> Dict[str, float]:
50
+ y_true = np.array(y_true)
51
+ y_pred = np.array(y_pred)
52
+ if SKLEARN_AVAILABLE:
53
+ mae = float(mean_absolute_error(y_true, y_pred))
54
+ rmse = float(np.sqrt(mean_squared_error(y_true, y_pred)))
55
+ else:
56
+ mae = float(np.mean(np.abs(y_true - y_pred)))
57
+ rmse = float(np.sqrt(np.mean((y_true - y_pred) ** 2)))
58
+ return {"MAE": mae, "RMSE": rmse}
59
+
60
+
61
+ def fit_auto_arima(series: pd.Series, seasonal: bool = False, m: int = 1, **kwargs):
62
+ """
63
+ Подбор ARIMA через pmdarima.auto_arima. Возвращает обученную модель pmdarima.
64
+ """
65
+ if not PM_AVAILABLE:
66
+ raise ImportError("pmdarima не установлен. Установите pmdarima (pip install pmdarima).")
67
+ if not is_pandas_series(series):
68
+ series = pd.Series(series)
69
+ series_clean = series.dropna()
70
+ if series_clean.empty:
71
+ raise ValueError("Пустая серия передана в fit_auto_arima.")
72
+ model = pm.auto_arima(series_clean, seasonal=seasonal, m=m, error_action="ignore", suppress_warnings=True, **kwargs)
73
+ return model
74
+
75
+
76
+ def fit_sarimax(series: pd.Series, order: Tuple[int, int, int] = (1, 0, 0),
77
+ seasonal_order: Tuple[int, int, int, int] = (0, 0, 0, 0),
78
+ enforce_stationarity: bool = False, enforce_invertibility: bool = True, **fit_kwargs):
79
+ """
80
+ Обучает SARIMAX (statsmodels). Возвращает результат fit() (SARIMAXResults).
81
+ """
82
+ if not STATSMODELS_AVAILABLE:
83
+ raise ImportError("statsmodels не установлен. Установите statsmodels.")
84
+ if not is_pandas_series(series):
85
+ series = pd.Series(series)
86
+ series_clean = series.dropna()
87
+ if series_clean.empty:
88
+ raise ValueError("Пустая серия передана в fit_sarimax.")
89
+ model = SARIMAX(series_clean, order=order, seasonal_order=seasonal_order,
90
+ enforce_stationarity=enforce_stationarity, enforce_invertibility=enforce_invertibility)
91
+ res = model.fit(disp=False, **fit_kwargs)
92
+ return res
93
+
94
+
95
+ def forecast_sarimax(fit_res, steps: int, alpha: float = 0.05) -> Tuple[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
96
+ """
97
+ Делает прогноз из обученного SARIMAX-результата (res.get_forecast).
98
+ Возвращает (mean, (lower, upper)) — numpy arrays длины steps.
99
+ """
100
+ if hasattr(fit_res, "get_forecast"):
101
+ fc = fit_res.get_forecast(steps=steps)
102
+ mean = np.asarray(fc.predicted_mean)
103
+ try:
104
+ conf = fc.conf_int(alpha=alpha)
105
+ lower = np.asarray(conf.iloc[:, 0])
106
+ upper = np.asarray(conf.iloc[:, 1])
107
+ except Exception:
108
+ lower = np.full(len(mean), np.nan)
109
+ upper = np.full(len(mean), np.nan)
110
+ return mean, (lower, upper)
111
+ else:
112
+ # fallback на forecast
113
+ try:
114
+ f = fit_res.forecast(steps=steps)
115
+ mean = np.asarray(f)
116
+ lower = np.full(len(mean), np.nan)
117
+ upper = np.full(len(mean), np.nan)
118
+ return mean, (lower, upper)
119
+ except Exception as e:
120
+ raise ValueError(f"Не удалось получить прогноз из объекта результата: {e}")
121
+
122
+
123
+ def fit_var(df: pd.DataFrame, maxlags: int = 15):
124
+ """
125
+ Обучает VAR на multivariate dataframe (pandas DataFrame). Возвращает fitted VARResults.
126
+ """
127
+ if not STATSMODELS_AVAILABLE:
128
+ raise ImportError("statsmodels не установлен. Установите statsmodels.")
129
+ if not isinstance(df, pd.DataFrame):
130
+ raise ValueError("fit_var ожидает pd.DataFrame с несколькими числовыми колонками.")
131
+ df_clean = df.dropna()
132
+ if df_clean.shape[0] < 3:
133
+ raise ValueError("Недостаточно наблюдений для VAR.")
134
+ model = VAR(df_clean)
135
+ sel = model.select_order(maxlags=maxlags)
136
+ best_lag = None
137
+ try:
138
+ if hasattr(sel, "selected_orders"):
139
+ so = sel.selected_orders
140
+ for k in ("aic", "bic", "fpe", "hqic"):
141
+ val = so.get(k, None)
142
+ if val is not None:
143
+ best_lag = int(val)
144
+ break
145
+ except Exception:
146
+ best_lag = None
147
+ if best_lag is None or best_lag < 1:
148
+ best_lag = 1
149
+ fitted = model.fit(maxlags=best_lag)
150
+ return fitted
151
+
152
+
153
+ def forecast_var(fitted_var, steps: int) -> pd.DataFrame:
154
+ """
155
+ Multi-step forecasting for VARResults. Возвращает DataFrame прогнозов (columns = variables).
156
+ """
157
+ try:
158
+ forecast = fitted_var.forecast(fitted_var.endog[-fitted_var.k_ar:], steps=steps)
159
+ cols = fitted_var.names
160
+ idx = range(1, steps + 1)
161
+ return pd.DataFrame(forecast, columns=cols, index=idx)
162
+ except Exception as e:
163
+ raise ValueError(f"Ошибка при прогнозе VAR: {e}")
164
+
165
+
166
+ def fit_garch(series: pd.Series, p: int = 1, q: int = 1):
167
+ """
168
+ Обучает GARCH(p,q) (arch package). Возвращает объект результата fit() из arch.
169
+ """
170
+ if not ARCH_AVAILABLE:
171
+ raise ImportError("arch не установлен. Установите arch (pip install arch).")
172
+ if not is_pandas_series(series):
173
+ series = pd.Series(series)
174
+ series_clean = series.dropna()
175
+ if series_clean.empty:
176
+ raise ValueError("Пустая серия передана в fit_garch.")
177
+ am = arch_model(series_clean, vol="Garch", p=p, q=q, dist="normal")
178
+ res = am.fit(disp="off")
179
+ return res
180
+
181
+
182
+ def safe_summary(obj) -> str:
183
+ try:
184
+ return str(obj.summary())
185
+ except Exception:
186
+ return repr(obj)
187
+
188
+
189
+ # краткий тест при запуске модуля напрямую
190
+ if __name__ == "__main__":
191
+ print("lab3_functions: доступные функции:",
192
+ [n for n in dir() if n.startswith("fit_") or n.startswith("forecast_")])
src/lab3_pipeline.py ADDED
@@ -0,0 +1,955 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # lab3_pipeline.py
2
+ """
3
+ Полный pipeline для ЛР №3 (выполнение пунктов 3.1-3.8).
4
+ Сохраняйте файл в папке src проекта (TimeSeriesHomework/src/lab3_pipeline.py).
5
+ """
6
+
7
+ import os
8
+ import math
9
+ import time
10
+ from typing import List, Dict, Any, Optional, Tuple
11
+
12
+ import numpy as np
13
+ import pandas as pd
14
+ import matplotlib.pyplot as plt
15
+
16
+ # statsmodels и основные тесты
17
+ try:
18
+ from statsmodels.tsa.stattools import adfuller, kpss
19
+ from statsmodels.tsa.seasonal import seasonal_decompose
20
+ from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
21
+ from statsmodels.stats.diagnostic import acorr_ljungbox
22
+ from statsmodels.tsa.statespace.sarimax import SARIMAX
23
+ from statsmodels.tsa.api import VAR
24
+
25
+ STATSMODELS_AVAILABLE = True
26
+ except Exception as e:
27
+ STATSMODELS_AVAILABLE = False
28
+ print("warning: statsmodels not available:", e)
29
+
30
+ # optional heavy deps
31
+ try:
32
+ import pmdarima as pm
33
+
34
+ PM_AVAILABLE = True
35
+ except Exception:
36
+ PM_AVAILABLE = False
37
+
38
+ try:
39
+ from arch import arch_model
40
+
41
+ ARCH_AVAILABLE = True
42
+ except Exception:
43
+ ARCH_AVAILABLE = False
44
+
45
+ try:
46
+ from prophet import Prophet
47
+
48
+ PROPHET_AVAILABLE = True
49
+ except Exception:
50
+ PROPHET_AVAILABLE = False
51
+
52
+ # sklearn
53
+ try:
54
+ from sklearn.model_selection import TimeSeriesSplit
55
+ from sklearn.linear_model import LinearRegression
56
+ from sklearn.metrics import r2_score
57
+
58
+ SKLEARN_AVAILABLE = True
59
+ except Exception:
60
+ SKLEARN_AVAILABLE = False
61
+
62
+ # scipy (Box-Cox, Shapiro)
63
+ try:
64
+ from scipy.stats import boxcox, boxcox_normmax, shapiro
65
+
66
+ SCIPY_AVAILABLE = True
67
+ except Exception:
68
+ SCIPY_AVAILABLE = False
69
+
70
+ try:
71
+ from tbats import TBATS
72
+ TBATS_AVAILABLE = True
73
+ except ImportError:
74
+ TBATS_AVAILABLE = False
75
+
76
+ # -------------------------------------------------------------------------
77
+ # Metrics
78
+ # -------------------------------------------------------------------------
79
+ def mae(y_true, y_pred): return np.mean(np.abs(y_true - y_pred))
80
+
81
+
82
+ def rmse(y_true, y_pred): return math.sqrt(np.mean((y_true - y_pred) ** 2))
83
+
84
+
85
+ def mape(y_true, y_pred): return np.mean(np.abs((y_true - y_pred) / (y_true + 1e-9))) * 100.0
86
+
87
+
88
+ def smape(y_true, y_pred): return 100.0 * np.mean(
89
+ 2.0 * np.abs(y_pred - y_true) / (np.abs(y_true) + np.abs(y_pred) + 1e-9))
90
+
91
+
92
+ def rmsle(y_true, y_pred): return math.sqrt(np.mean((np.log1p(y_pred) - np.log1p(y_true)) ** 2))
93
+
94
+
95
+ def mase(y_true, y_pred, naive_ref):
96
+ # naive_ref: series used to compute naive diff (e.g. train series)
97
+ denom = np.mean(np.abs(np.diff(naive_ref)))
98
+ if denom == 0:
99
+ return np.nan
100
+ return np.mean(np.abs(y_true - y_pred)) / denom
101
+
102
+
103
+ # -------------------------------------------------------------------------
104
+ # IO & preprocessing utilities
105
+ # -------------------------------------------------------------------------
106
+ def load_data(path: str, timestamp_col: str = 'timestamp', tz: Optional[str] = None) -> pd.DataFrame:
107
+ if path.endswith('.parquet'):
108
+ df = pd.read_parquet(path)
109
+ else:
110
+ df = pd.read_csv(path)
111
+ if timestamp_col not in df.columns:
112
+ raise ValueError(f"timestamp column '{timestamp_col}' not found")
113
+ df[timestamp_col] = pd.to_datetime(df[timestamp_col], errors='coerce')
114
+ if tz is not None:
115
+ try:
116
+ df[timestamp_col] = df[timestamp_col].dt.tz_localize(tz)
117
+ except Exception:
118
+ try:
119
+ df[timestamp_col] = df[timestamp_col].dt.tz_convert(tz)
120
+ except Exception:
121
+ pass
122
+ df = df.sort_values(timestamp_col).drop_duplicates(subset=[timestamp_col])
123
+ df = df.set_index(timestamp_col)
124
+ return df
125
+
126
+
127
+ def resample_and_interpolate(df: pd.DataFrame, freq: str = 'D', method: str = 'linear') -> pd.DataFrame:
128
+ dfr = df.resample(freq).asfreq()
129
+ if method == 'linear':
130
+ return dfr.interpolate(method='linear')
131
+ elif method == 'ffill':
132
+ return dfr.fillna(method='ffill')
133
+ else:
134
+ return dfr.fillna(method='ffill').interpolate()
135
+
136
+
137
+ # -------------------------------------------------------------------------
138
+ # Transformations and stationarity selection
139
+ # -------------------------------------------------------------------------
140
+ def test_stationarity_pair(series: pd.Series) -> Dict[str, Dict[str, Any]]:
141
+ """Возвращает результаты ADF и KPSS"""
142
+ res = {}
143
+ if not STATSMODELS_AVAILABLE:
144
+ raise ImportError("statsmodels required for stationarity tests")
145
+ s = series.dropna()
146
+ if len(s) < 3:
147
+ return {'adf': {'pvalue': np.nan}, 'kpss': {'pvalue': np.nan}}
148
+ adf_res = adfuller(s, autolag='AIC', regression='c')
149
+ kpss_res = kpss(s, nlags='auto')
150
+ return {'adf': {'stat': adf_res[0], 'pvalue': adf_res[1]}, 'kpss': {'stat': kpss_res[0], 'pvalue': kpss_res[1]}}
151
+
152
+
153
+ def try_transformations_and_choose(y_train: pd.Series, seasonal_period: int = 7):
154
+ """
155
+ Пробуем набор преобразований:
156
+ - none
157
+ - log (если >0)
158
+ - boxcox (если >0 и scipy доступен)
159
+ - diff(1), diff(s), diff(1).diff(s)
160
+ Выбираем ту комбинацию, которая минимизирует конфликт ADF/KPSS:
161
+ критерий: ADF.pvalue < 0.05 (хочется) и KPSS.pvalue > 0.05 (хочется).
162
+ Возвращаем: transformed_series, meta dict (applied transformations, lambda)
163
+ """
164
+ candidates = []
165
+ # original
166
+ candidates.append(('none', y_train))
167
+ # log
168
+ if (y_train > 0).all():
169
+ candidates.append(('log', np.log(y_train)))
170
+ # boxcox
171
+ if (y_train > 0).all() and SCIPY_AVAILABLE:
172
+ try:
173
+ lam = boxcox_normmax(y_train.dropna(), brack=(-2, 2))
174
+ bc, _ = apply_boxcox(y_train, lmbda=lam)
175
+ candidates.append((f'boxcox_{lam:.4f}', bc))
176
+ except Exception:
177
+ pass
178
+ # differenced versions
179
+ # diff1 of original or of transformed series
180
+ final_candidates = []
181
+ for name, ser in candidates:
182
+ ser_clean = ser.dropna()
183
+ final_candidates.append((name, 0, ser_clean)) # 0 differences
184
+ if len(ser_clean) > 3:
185
+ final_candidates.append((name, 1, ser_clean.diff(1).dropna()))
186
+ if seasonal_period and len(ser_clean) > seasonal_period + 3:
187
+ final_candidates.append((name, seasonal_period, ser_clean.diff(seasonal_period).dropna()))
188
+ final_candidates.append(
189
+ (name, ('1+s', seasonal_period), ser_clean.diff(1).diff(seasonal_period).dropna()))
190
+ # Evaluate candidates
191
+ scored = []
192
+ for cand in final_candidates:
193
+ tag = cand[0]
194
+ d = cand[1]
195
+ ser = cand[2]
196
+ if ser.dropna().shape[0] < 10:
197
+ continue
198
+ try:
199
+ tests = test_stationarity_pair(ser)
200
+ # score: lower is better. We want ADF.p < 0.05 and KPSS.p > 0.05.
201
+ adf_p = tests['adf']['pvalue'] if tests['adf']['pvalue'] is not None else 1.0
202
+ kpss_p = tests['kpss']['pvalue'] if tests['kpss']['pvalue'] is not None else 0.0
203
+ # penalty for bad ADF (want small) and bad KPSS (want big)
204
+ score = (adf_p) + (1.0 - kpss_p)
205
+ scored.append({'tag': tag, 'diff': d, 'score': score, 'adf_p': adf_p, 'kpss_p': kpss_p, 'series': ser})
206
+ except Exception:
207
+ continue
208
+ if not scored:
209
+ return y_train, {'method': 'none', 'lambda': None}
210
+ scored = sorted(scored, key=lambda x: x['score'])
211
+ best = scored[0]
212
+ meta = {'method': best['tag'], 'diff': best['diff'], 'adf_p': best['adf_p'], 'kpss_p': best['kpss_p']}
213
+ return best['series'], meta
214
+
215
+
216
+ def apply_boxcox(series: pd.Series, lmbda: Optional[float] = None):
217
+ if not SCIPY_AVAILABLE:
218
+ raise ImportError("scipy required for boxcox")
219
+ s = series.dropna()
220
+ if (s <= 0).any():
221
+ raise ValueError("Box-Cox requires positive values")
222
+ if lmbda is None:
223
+ lmbda = boxcox_normmax(s, brack=(-2, 2))
224
+ transformed = boxcox(s, lmbda)
225
+ return pd.Series(index=s.index, data=transformed), float(lmbda)
226
+
227
+
228
+ # -------------------------------------------------------------------------
229
+ # Feature engineering
230
+ # -------------------------------------------------------------------------
231
+ def make_lags(df: pd.DataFrame, col: str, lags: List[int]):
232
+ for l in lags:
233
+ df[f'{col}_lag_{l}'] = df[col].shift(l)
234
+ return df
235
+
236
+
237
+ def make_rolls(df: pd.DataFrame, col: str, windows: List[int]):
238
+ for w in windows:
239
+ df[f'{col}_roll_mean_{w}'] = df[col].rolling(window=w, min_periods=1).mean()
240
+ df[f'{col}_roll_std_{w}'] = df[col].rolling(window=w, min_periods=1).std()
241
+ df[f'{col}_roll_min_{w}'] = df[col].rolling(window=w, min_periods=1).min()
242
+ df[f'{col}_roll_max_{w}'] = df[col].rolling(window=w, min_periods=1).max()
243
+ return df
244
+
245
+
246
+ def make_time_features(df: pd.DataFrame):
247
+ idx = df.index
248
+ df['dayofweek'] = idx.dayofweek
249
+ df['month'] = idx.month
250
+ df['is_weekend'] = idx.dayofweek >= 5
251
+ df['sin_week'] = np.sin(2 * np.pi * df['dayofweek'] / 7)
252
+ df['cos_month'] = np.cos(2 * np.pi * (df['month'] - 1) / 12)
253
+ return df
254
+
255
+
256
+ # -------------------------------------------------------------------------
257
+ # Splits, CV and strategies
258
+ # -------------------------------------------------------------------------
259
+ def chronological_split(df: pd.DataFrame, frac_train=0.7, frac_val=0.15):
260
+ n = len(df)
261
+ i_train = int(n * frac_train)
262
+ i_val = i_train + int(n * frac_val)
263
+
264
+ train = df.iloc[:i_train].copy()
265
+ val = df.iloc[i_train:i_val].copy()
266
+ test = df.iloc[i_val:].copy()
267
+
268
+ # Проверяем непрерывность дат
269
+ all_data = pd.concat([train, val, test])
270
+ date_diff = (all_data.index[1:] - all_data.index[:-1]).value_counts()
271
+
272
+ if len(date_diff) > 1:
273
+ print(f"Предупреждение: обнаружены разные интервалы между датами: {date_diff.index.tolist()}")
274
+
275
+ return train, val, test
276
+
277
+
278
+ def expanding_window_cv(X: pd.DataFrame, y: pd.Series, model_fit_predict, initial_train_size: int, h: int,
279
+ n_splits: int = 5):
280
+ """Expanding window: [0:t] -> [t+1:t+h]"""
281
+ n = len(X)
282
+ step = (n - initial_train_size - h) // n_splits if n_splits > 0 else h
283
+ metrics = []
284
+ for i in range(n_splits):
285
+ end_train = initial_train_size + i * step
286
+ train_X, train_y = X.iloc[:end_train], y.iloc[:end_train]
287
+ test_X, test_y = X.iloc[end_train:end_train + h], y.iloc[end_train:end_train + h]
288
+ y_pred = model_fit_predict(train_X, train_y, h)
289
+ metrics.append({'fold': i, 'mae': mae(test_y.values, y_pred), 'rmse': rmse(test_y.values, y_pred)})
290
+ return pd.DataFrame(metrics)
291
+
292
+
293
+ def rolling_window_cv(X: pd.DataFrame, y: pd.Series, model_fit_predict, window: int, h: int, n_splits: int = 5):
294
+ """Rolling window: [t-w:t] -> [t+1:t+h]"""
295
+ n = len(X)
296
+ step = (n - window - h) // n_splits if n_splits > 0 else h
297
+ metrics = []
298
+ for i in range(n_splits):
299
+ start = i * step
300
+ end = start + window
301
+ train_X, train_y = X.iloc[start:end], y.iloc[start:end]
302
+ test_X, test_y = X.iloc[end:end + h], y.iloc[end:end + h]
303
+ y_pred = model_fit_predict(train_X, train_y, h)
304
+ metrics.append({'fold': i, 'mae': mae(test_y.values, y_pred), 'rmse': rmse(test_y.values, y_pred)})
305
+ return pd.DataFrame(metrics)
306
+
307
+
308
+ # Strategies: recursive, direct, hybrid
309
+ def forecast_recursive_arima(fit_res, steps: int, last_date: pd.Timestamp = None, freq: str = 'D'):
310
+ """Wrapper for SARIMAX results with proper date index"""
311
+ if hasattr(fit_res, "get_forecast"):
312
+ fc = fit_res.get_forecast(steps=steps)
313
+ mean = np.asarray(fc.predicted_mean)
314
+ try:
315
+ conf = fc.conf_int()
316
+ low = np.asarray(conf.iloc[:, 0])
317
+ high = np.asarray(conf.iloc[:, 1])
318
+ except Exception:
319
+ low = np.full(len(mean), np.nan)
320
+ high = np.full(len(mean), np.nan)
321
+
322
+ # Создаем правильный индекс
323
+ if last_date is not None:
324
+ dates = create_forecast_index(last_date, steps, freq)
325
+ mean = pd.Series(mean, index=dates)
326
+ low = pd.Series(low, index=dates)
327
+ high = pd.Series(high, index=dates)
328
+
329
+ return mean, (low, high)
330
+ else:
331
+ mean = fit_res.forecast(steps=steps)
332
+ if last_date is not None:
333
+ dates = create_forecast_index(last_date, steps, freq)
334
+ mean = pd.Series(mean, index=dates)
335
+ return mean, (None, None)
336
+
337
+
338
+ # Direct strategy for SARIMAX: fit separate models for each horizon
339
+ def forecast_direct_arima(train_series: pd.Series, h: int, order=(1, 0, 0)):
340
+ if not STATSMODELS_AVAILABLE:
341
+ raise ImportError("statsmodels required")
342
+ # create shifted target for forecasting h steps ahead
343
+ df = train_series.to_frame("y")
344
+ df['y_target_h'] = df['y'].shift(-h)
345
+ df = df.dropna()
346
+ # naive approach: use previous value as predictor (simple)
347
+ last = train_series.iloc[-1]
348
+ return np.full(h, last)
349
+
350
+
351
+ # -------------------------------------------------------------------------
352
+ # Models training wrapper
353
+ # -------------------------------------------------------------------------
354
+ def fit_sarimax_simple(series: pd.Series, order=(1, 0, 0), seasonal_order=(0, 0, 0, 0), **kwargs):
355
+ if not STATSMODELS_AVAILABLE:
356
+ raise ImportError("statsmodels required")
357
+ m = SARIMAX(series.dropna(), order=order, seasonal_order=seasonal_order,
358
+ enforce_stationarity=False, enforce_invertibility=False)
359
+ res = m.fit(disp=False, **kwargs)
360
+ return res
361
+
362
+
363
+ def forecast_sarimax(fit_res, steps: int, alpha: float = 0.05) -> Tuple[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
364
+ """
365
+ Делает прогноз из обученного SARIMAX-результата.
366
+ Возвращает (mean, (lower, upper)) — numpy arrays длины steps.
367
+ """
368
+ try:
369
+ if hasattr(fit_res, "get_forecast"):
370
+ fc = fit_res.get_forecast(steps=steps)
371
+ mean = np.asarray(fc.predicted_mean)
372
+
373
+ # Проверяем на NaN
374
+ if np.any(np.isnan(mean)):
375
+ # Fallback: используем простой forecast
376
+ mean = fit_res.forecast(steps=steps)
377
+ mean = np.asarray(mean)
378
+
379
+ try:
380
+ conf = fc.conf_int(alpha=alpha)
381
+ lower = np.asarray(conf.iloc[:, 0])
382
+ upper = np.asarray(conf.iloc[:, 1])
383
+
384
+ # Проверяем доверительные интервалы на NaN
385
+ if np.any(np.isnan(lower)) or np.any(np.isnan(upper)):
386
+ lower = np.full(len(mean), np.nan)
387
+ upper = np.full(len(mean), np.nan)
388
+
389
+ except Exception:
390
+ lower = np.full(len(mean), np.nan)
391
+ upper = np.full(len(mean), np.nan)
392
+
393
+ return mean, (lower, upper)
394
+ else:
395
+ # fallback на forecast
396
+ mean = fit_res.forecast(steps=steps)
397
+ mean = np.asarray(mean)
398
+ lower = np.full(len(mean), np.nan)
399
+ upper = np.full(len(mean), np.nan)
400
+ return mean, (lower, upper)
401
+
402
+ except Exception as e:
403
+ # Если все методы не сработали, возвращаем массив NaN
404
+ print(f"Warning: SARIMAX forecast failed: {e}")
405
+ mean = np.full(steps, np.nan)
406
+ lower = np.full(steps, np.nan)
407
+ upper = np.full(steps, np.nan)
408
+ return mean, (lower, upper)
409
+
410
+ def fit_auto_arima(series: pd.Series, seasonal=False, m=1, **kwargs):
411
+ if not PM_AVAILABLE:
412
+ raise ImportError("pmdarima not installed")
413
+ model = pm.auto_arima(series.dropna(), seasonal=seasonal, m=m, error_action='ignore', suppress_warnings=True,
414
+ **kwargs)
415
+ return model
416
+
417
+
418
+ def fit_var(df: pd.DataFrame, maxlags=15):
419
+ if not STATSMODELS_AVAILABLE:
420
+ raise ImportError("statsmodels required")
421
+ model = VAR(df.dropna())
422
+ sel = model.select_order(maxlags=maxlags)
423
+ best = 1
424
+ try:
425
+ so = sel.selected_orders
426
+ for k in ('aic', 'bic', 'fpe', 'hqic'):
427
+ if so.get(k) is not None:
428
+ best = int(so[k])
429
+ break
430
+ except Exception:
431
+ best = 1
432
+ res = model.fit(maxlags=best)
433
+ return res
434
+
435
+
436
+ def fit_garch_on_residuals(residuals, p=1, q=1):
437
+ if not ARCH_AVAILABLE:
438
+ raise ImportError("arch not installed")
439
+ am = arch_model(residuals, vol='Garch', p=p, q=q, dist='normal')
440
+ r = am.fit(disp='off')
441
+ return r
442
+
443
+
444
+ # -------------------------------------------------------------------------
445
+ # Diagnostics and tests
446
+ # -------------------------------------------------------------------------
447
+ def ljung_box_test(resid: np.ndarray, lags: List[int] = [10]):
448
+ if not STATSMODELS_AVAILABLE:
449
+ raise ImportError("statsmodels required")
450
+ res = acorr_ljungbox(resid, lags=lags, return_df=True)
451
+ return res
452
+
453
+
454
+ def shapiro_test(resid: np.ndarray):
455
+ if not SCIPY_AVAILABLE:
456
+ raise ImportError("scipy required")
457
+ stat, p = shapiro(resid)
458
+ return {'stat': stat, 'pvalue': p}
459
+
460
+
461
+ def simple_dm_test(e1: np.ndarray, e2: np.ndarray):
462
+ """
463
+ Простая реализация Diebold-Mariano теста по разности квадратических ошибок.
464
+ Возвращает t-stat и p-value (двухсторонний).
465
+ Примечание: это упрощённая версия, без HAC коррекции.
466
+ """
467
+ # use squared error loss
468
+ d = (e1 - e2)
469
+ n = len(d)
470
+ dbar = np.mean(d)
471
+ sd = np.var(d, ddof=1)
472
+ denom = math.sqrt(sd / n) if sd > 0 else np.nan
473
+ if denom == 0 or np.isnan(denom):
474
+ return {'stat': np.nan, 'pvalue': np.nan}
475
+ tstat = dbar / denom
476
+ # two-sided pval from Student's t approx
477
+ from scipy.stats import t as student_t
478
+ pval = 2 * (1 - student_t.cdf(abs(tstat), df=n - 1))
479
+ return {'stat': float(tstat), 'pvalue': float(pval)}
480
+
481
+
482
+ # -------------------------------------------------------------------------
483
+ # Report generation (HTML)
484
+ # -------------------------------------------------------------------------
485
+ def generate_report_html(out_path: str, plots: List[plt.Figure], tables: Dict[str, pd.DataFrame], title="Lab3 Report"):
486
+ import base64
487
+ from io import BytesIO
488
+
489
+ html_parts = [f"""
490
+ <html>
491
+ <head>
492
+ <meta charset='utf-8'>
493
+ <title>{title}</title>
494
+ <style>
495
+ body {{ font-family: Arial, sans-serif; margin: 20px; background-color: white; color: black; }}
496
+ table {{ border-collapse: collapse; width: 100%; margin: 10px 0; }}
497
+ th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
498
+ th {{ background-color: #f2f2f2; }}
499
+ img {{ max-width: 100%; height: auto; margin: 10px 0; }}
500
+ .table-container {{ overflow-x: auto; }}
501
+ </style>
502
+ </head>
503
+ <body>
504
+ <h1>{title}</h1>
505
+ """]
506
+
507
+ # Таблицы
508
+ for name, df in tables.items():
509
+ html_parts.append(f"<h2>{name}</h2>")
510
+ html_parts.append('<div class="table-container">')
511
+ html_parts.append(df.to_html(classes='table table-striped', border=0, index=True))
512
+ html_parts.append('</div>')
513
+
514
+ # Графики как base64
515
+ for i, fig in enumerate(plots):
516
+ # Сохраняем рисунок в буфер
517
+ buf = BytesIO()
518
+ fig.savefig(buf, format='png', bbox_inches='tight', dpi=100)
519
+ buf.seek(0)
520
+
521
+ # Кодируем в base64
522
+ img_data = base64.b64encode(buf.read()).decode('utf-8')
523
+ html_parts.append(f'<h3>Figure {i + 1}</h3>')
524
+ html_parts.append(f'<img src="data:image/png;base64,{img_data}" alt="Figure {i + 1}">')
525
+
526
+ # Закрываем рисунок чтобы освободить память
527
+ plt.close(fig)
528
+ break
529
+
530
+ html_parts.append("</body></html>")
531
+
532
+ with open(out_path, 'w', encoding='utf-8') as f:
533
+ f.write("\n".join(html_parts))
534
+ print("Report saved to", out_path)
535
+
536
+ # -------------------------------------------------------------------------
537
+ # Main runner that orchestrates everything
538
+ # -------------------------------------------------------------------------
539
+ def evaluate_with_cv(models_dict, X, y, cv_method='expanding', n_splits=5):
540
+ """Оценка моделей с кросс-валидацией"""
541
+ cv_results = {}
542
+
543
+ for name, model_func in models_dict.items():
544
+ if cv_method == 'expanding':
545
+ cv_scores = expanding_window_cv(X, y, model_func,
546
+ initial_train_size=len(X) // 2,
547
+ h=30, n_splits=n_splits)
548
+ else:
549
+ cv_scores = rolling_window_cv(X, y, model_func,
550
+ window=len(X) // 2,
551
+ h=30, n_splits=n_splits)
552
+ cv_results[name] = cv_scores
553
+
554
+ return cv_results
555
+
556
+ def run_pipeline(data_path: str, timestamp_col: str, target_col: str,
557
+ out_report: str = 'lab3_report.html', freq: str = 'D'):
558
+
559
+ """
560
+ Главная точка запуска pipeline.
561
+ """
562
+ print("Loading", data_path)
563
+ df = load_data(data_path, timestamp_col)
564
+ if target_col not in df.columns:
565
+ numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
566
+ if not numeric_cols:
567
+ raise ValueError("No numeric columns found")
568
+ target_col = numeric_cols[0]
569
+ print("Target not found, using", target_col)
570
+
571
+ series = df[target_col].astype('float').copy()
572
+ series = resample_and_interpolate(series.to_frame(), freq=freq).iloc[:, 0]
573
+ print("Series length after resample:", len(series))
574
+
575
+ # 3.1 Preprocessing & transformation selection
576
+ transformed, meta = try_transformations_and_choose(series, seasonal_period=7)
577
+ print("Chosen transform:", meta)
578
+
579
+ # 3.2 Feature engineering
580
+ df_all = transformed.to_frame(name=target_col)
581
+ df_all = make_time_features(df_all)
582
+ df_all = make_lags(df_all, target_col, [1, 2, 7, 30])
583
+ df_all = make_rolls(df_all, target_col, [7, 30])
584
+
585
+ # dropna rows with lag features
586
+ df_all = df_all.dropna()
587
+ train, val, test = chronological_split(df_all, frac_train=0.7, frac_val=0.15)
588
+ y_train = train[target_col];
589
+ y_val = val[target_col];
590
+ y_test = test[target_col]
591
+ print("Sizes train/val/test:", len(y_train), len(y_val), len(y_test))
592
+
593
+ # 3.3 Models: benchmarks + SARIMAX + optional auto_arima + VAR
594
+ results = [] # each elem: dict(model, h, preds (np.array), extra)
595
+ horizons = [1, 7, 30]
596
+
597
+ # Определяем частоту для прогнозов
598
+ try:
599
+ inferred_freq = pd.infer_freq(y_train.index) or freq
600
+ except:
601
+ inferred_freq = freq
602
+
603
+ # Benchmarks
604
+ for h in horizons:
605
+ pred_values = np.full(h, y_train.iloc[-1])
606
+ pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
607
+ results.append({
608
+ 'model': 'naive',
609
+ 'h': h,
610
+ 'pred': pd.Series(pred_values, index=pred_dates)
611
+ })
612
+
613
+ if len(y_train) >= 7:
614
+ seasonal_pred = seasonal_naive_forecast(y_train, season=7, steps=h)
615
+ seasonal_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
616
+ results.append({
617
+ 'model': 'seasonal_naive',
618
+ 'h': h,
619
+ 'pred': pd.Series(seasonal_pred, index=seasonal_dates)
620
+ })
621
+
622
+ # SES/Holt (simple forecasting for 1-step and iterated for multi-step)
623
+ try:
624
+ from statsmodels.tsa.holtwinters import SimpleExpSmoothing, ExponentialSmoothing
625
+ # SES as simple baseline
626
+ ses = SimpleExpSmoothing(y_train.dropna()).fit(optimized=True)
627
+ for h in horizons:
628
+ pred = ses.forecast(h)
629
+ pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
630
+ results.append({
631
+ 'model': 'SES',
632
+ 'h': h,
633
+ 'pred': pd.Series(pred, index=pred_dates)
634
+ })
635
+ except Exception as e:
636
+ print("SES skipped:", e)
637
+
638
+ # SARIMAX baseline
639
+ if STATSMODELS_AVAILABLE:
640
+ try:
641
+ # Проверяем, что данные подходят для SARIMAX
642
+ if len(y_train.dropna()) > 10 and y_train.var() > 1e-6: # достаточное количество точек и дисперсия
643
+ sar = fit_sarimax_simple(y_train, order=(1, 1, 1))
644
+
645
+ # Проверяем, что модель сходилась
646
+ if hasattr(sar, 'mle_retvals') and sar.mle_retvals.get('converged', False):
647
+ for h in horizons:
648
+ mean, (lower, upper) = forecast_sarimax(sar, steps=h)
649
+
650
+ # Проверяем, что прогнозы не все NaN
651
+ if not np.all(np.isnan(mean)):
652
+ pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
653
+ results.append({
654
+ 'model': 'SARIMAX(1,1,1)',
655
+ 'h': h,
656
+ 'pred': pd.Series(mean, index=pred_dates)
657
+ })
658
+ else:
659
+ print(f"SARIMAX returned all NaN for horizon {h}")
660
+ else:
661
+ print("SARIMAX model did not converge")
662
+ else:
663
+ print("Insufficient data for SARIMAX")
664
+ except Exception as e:
665
+ print("SARIMAX failed:", e)
666
+
667
+ # pmdarima auto_arima
668
+ if PM_AVAILABLE:
669
+ try:
670
+ auto = fit_auto_arima(y_train, seasonal=False)
671
+ for h in horizons:
672
+ p = auto.predict(n_periods=h)
673
+ pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
674
+ results.append({
675
+ 'model': 'auto_arima',
676
+ 'h': h,
677
+ 'pred': pd.Series(p, index=pred_dates)
678
+ })
679
+ except Exception as e:
680
+ print("auto_arima failed:", e)
681
+
682
+ # VAR if multivariate
683
+ if STATSMODELS_AVAILABLE and df.select_dtypes(include=[np.number]).shape[1] >= 2:
684
+ try:
685
+ num_df = df.select_dtypes(include=[np.number]).dropna()
686
+ var_res = fit_var(num_df, maxlags=5)
687
+ fut = var_res.forecast(var_res.endog[-var_res.k_ar:], steps=30)
688
+ # fut is array shape (30, k)
689
+ # wrap as predictions per horizon for the first variable
690
+ for h in [30]:
691
+ pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
692
+ results.append({
693
+ 'model': 'VAR',
694
+ 'h': h,
695
+ 'pred': pd.Series(fut[:h, 0], index=pred_dates)
696
+ })
697
+ except Exception as e:
698
+ print("VAR failed:", e)
699
+
700
+ # TBATS модель
701
+ if TBATS_AVAILABLE:
702
+ try:
703
+ tbats_model = TBATS(seasonal_periods=[7, 30], use_arma_errors=True)
704
+ tbats_fitted = tbats_model.fit(y_train)
705
+ for h in horizons:
706
+ tbats_pred = tbats_fitted.forecast(steps=h)
707
+ pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
708
+ results.append({
709
+ 'model': 'TBATS',
710
+ 'h': h,
711
+ 'pred': pd.Series(tbats_pred, index=pred_dates)
712
+ })
713
+ except Exception as e:
714
+ print("TBATS failed:", e)
715
+
716
+ # Prophet модель
717
+ if PROPHET_AVAILABLE:
718
+ try:
719
+ prophet_df = y_train.reset_index()
720
+ prophet_df.columns = ['ds', 'y']
721
+ prophet_model = Prophet()
722
+ prophet_model.fit(prophet_df)
723
+ future = prophet_model.make_future_dataframe(periods=max(horizons), freq=inferred_freq)
724
+ forecast = prophet_model.predict(future)
725
+ for h in horizons:
726
+ prophet_pred = forecast.tail(h)['yhat'].values
727
+ pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
728
+ results.append({
729
+ 'model': 'Prophet',
730
+ 'h': h,
731
+ 'pred': pd.Series(prophet_pred, index=pred_dates)
732
+ })
733
+ except Exception as e:
734
+ print("Prophet failed:", e)
735
+
736
+ # GARCH на остатках SARIMAX
737
+ if ARCH_AVAILABLE and 'sar' in locals():
738
+ try:
739
+ garch_model = fit_garch_on_residuals(sar.resid, p=1, q=1)
740
+ # Прогноз волатильности можно добавить в анализ
741
+ except Exception as e:
742
+ print("GARCH failed:", e)
743
+
744
+ # 3.6 Diagnostics later for top models
745
+ # 3.7 Evaluate on test set
746
+ eval_rows = []
747
+ plots = []
748
+
749
+ for rec in results:
750
+ model_name = rec['model']
751
+ h = rec['h']
752
+ pred = rec['pred']
753
+
754
+ # Выравниваем прогнозы с тестовыми данными по времени
755
+ if hasattr(pred, 'index'):
756
+ # Для прогнозов с правильным индексом
757
+ aligned_pred = pred
758
+ # Берем только первые h точек тестовых данных для сравнения
759
+ y_true_aligned = y_test.iloc[:min(h, len(y_test))]
760
+ else:
761
+ # Для прогнозов без индекса (старый формат)
762
+ pred_values = np.asarray(pred).ravel()
763
+ aligned_pred = pd.Series(pred_values, index=y_test.index[:len(pred_values)])
764
+ y_true_aligned = y_test.iloc[:len(pred_values)]
765
+
766
+ if len(y_true_aligned) == 0:
767
+ continue
768
+
769
+ # Обрезаем прогноз до длины тестовых данных
770
+ aligned_pred = aligned_pred.iloc[:len(y_true_aligned)]
771
+
772
+ # Вычисляем метрики
773
+ row = {
774
+ 'model': model_name,
775
+ 'h': h,
776
+ 'MAE': mae(y_true_aligned.values, aligned_pred.values),
777
+ 'RMSE': rmse(y_true_aligned.values, aligned_pred.values),
778
+ 'MAPE': mape(y_true_aligned.values, aligned_pred.values),
779
+ 'SMAPE': smape(y_true_aligned.values, aligned_pred.values)
780
+ }
781
+ # MASE: use naive in-sample reference
782
+ row['MASE'] = mase(y_true_aligned.values, aligned_pred.values, y_train.values)
783
+ # R2 where possible
784
+ try:
785
+ row['R2'] = float((1 - np.sum((y_true_aligned.values - aligned_pred.values) ** 2) / np.sum(
786
+ (y_true_aligned.values - np.mean(y_true_aligned.values)) ** 2)))
787
+ except Exception:
788
+ row['R2'] = np.nan
789
+ eval_rows.append(row)
790
+
791
+ # Визуализация
792
+ fig, ax = plt.subplots(figsize=(8, 3))
793
+
794
+ # Показываем больше данных для контекста
795
+ context_points = min(200, len(y_train))
796
+ ax.plot(y_train.index[-context_points:], y_train.values[-context_points:],
797
+ label='train', alpha=0.7)
798
+
799
+ if len(val) > 0:
800
+ ax.plot(val.index, val.values, label='val', alpha=0.7)
801
+
802
+ ax.plot(y_test.index, y_test.values, label='test', alpha=0.7)
803
+
804
+ # Прогнозы с правильными датами
805
+ ax.plot(aligned_pred.index, aligned_pred.values,
806
+ label=f'pred_{model_name}_h{h}', linewidth=2)
807
+
808
+ ax.legend()
809
+ plots.append(fig)
810
+
811
+ eval_df = pd.DataFrame(eval_rows)
812
+
813
+ # Diagnostics for top-3 by RMSE
814
+ diag_tables = {}
815
+ try:
816
+ top3 = eval_df.sort_values('RMSE').head(3)['model'].tolist()
817
+ except Exception:
818
+ top3 = []
819
+ for m in top3:
820
+ # find corresponding fitted residuals if model was SARIMAX etc.
821
+ if m.startswith('SARIMAX'):
822
+ try:
823
+ resid = sar.resid.dropna()
824
+ lb = acorr_ljungbox(resid, lags=[10], return_df=True)
825
+ diag_tables[f'ljungbox_{m}'] = lb
826
+ if SCIPY_AVAILABLE:
827
+ sh = shapiro(resid)
828
+ diag_tables[f'shapiro_{m}'] = pd.DataFrame([{'stat': sh[0], 'pvalue': sh[1]}])
829
+ except Exception:
830
+ pass
831
+
832
+ # Diebold-Mariano pairwise for top 2 models (if available)
833
+ dm_table = None
834
+ try:
835
+ if len(eval_df) >= 2:
836
+ sorted_models = eval_df.sort_values('RMSE')
837
+ if len(sorted_models) >= 2:
838
+ m1 = sorted_models.iloc[0]['model']
839
+ m2 = sorted_models.iloc[1]['model']
840
+ # pick their predictions at h=1 (if exist)
841
+ pred1 = None;
842
+ pred2 = None
843
+ for rec in results:
844
+ if rec['model'] == m1 and rec['h'] == 1:
845
+ pred1 = rec['pred']
846
+ if rec['model'] == m2 and rec['h'] == 1:
847
+ pred2 = rec['pred']
848
+ if pred1 is not None and pred2 is not None:
849
+ # align lengths with test
850
+ y_true = y_test.values[:min(len(pred1), len(y_test))]
851
+ e1 = (y_true - pred1.values[:len(y_true)]) ** 2
852
+ e2 = (y_true - pred2.values[:len(y_true)]) ** 2
853
+ dm = simple_dm_test(e1, e2)
854
+ dm_table = pd.DataFrame(
855
+ [{'model1': m1, 'model2': m2, 'dm_stat': dm['stat'], 'pvalue': dm['pvalue']}])
856
+ except Exception:
857
+ dm_table = None
858
+
859
+ # Generate report
860
+ tables = {'evaluation': eval_df}
861
+ if diag_tables:
862
+ tables.update(diag_tables)
863
+ if dm_table is not None:
864
+ tables['dm_test'] = dm_table
865
+
866
+ generate_report_html(out_report, plots, tables, title="Lab3 Full Report")
867
+ print("Pipeline finished. Report:", out_report)
868
+
869
+ # Ensure we have at least some predictions
870
+ if not results:
871
+ st.warning("Все модели вернули NaN. Использую простой наивный прогноз.")
872
+ for h in horizons:
873
+ pred_values = np.full(h, y_train.iloc[-1] if len(y_train) > 0 else 0)
874
+ pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
875
+ results.append({
876
+ 'model': 'fallback_naive',
877
+ 'h': h,
878
+ 'pred': pd.Series(pred_values, index=pred_dates)
879
+ })
880
+
881
+ cv_results = evaluate_with_cv({
882
+ 'SARIMAX': lambda X, y, h: forecast_recursive(fit_sarimax_simple(y), y, h),
883
+ 'AutoARIMA': lambda X, y, h: forecast_recursive(fit_auto_arima(y), y, h)
884
+ }, df_all.drop(columns=[target_col]), df_all[target_col])
885
+
886
+
887
+ # -------------------------
888
+ # helpers used in the pipeline but defined later
889
+ # -------------------------
890
+ def seasonal_naive_forecast(series: pd.Series, season: int, steps: int):
891
+ last = series.iloc[-season:]
892
+ reps = int(np.ceil(steps / season))
893
+ arr = np.tile(last.values, reps)[:steps]
894
+ return arr
895
+
896
+
897
+ def create_forecast_index(last_train_date: pd.Timestamp, steps: int, freq: str = 'D') -> pd.DatetimeIndex:
898
+ """Создает правильный временной индекс для прогнозов"""
899
+ try:
900
+ # Если freq = 'auto', пытаемся определить частоту
901
+ if freq == 'auto':
902
+ freq = pd.infer_freq(pd.DatetimeIndex([last_train_date])) or 'D'
903
+
904
+ # Создаем индекс с правильным смещением
905
+ if isinstance(last_train_date, pd.Timestamp):
906
+ start_date = last_train_date + pd.Timedelta(days=1)
907
+ else:
908
+ start_date = last_train_date + pd.DateOffset(days=1)
909
+
910
+ return pd.date_range(
911
+ start=start_date,
912
+ periods=steps,
913
+ freq=freq
914
+ )
915
+ except Exception as e:
916
+ print(f"Warning: could not create proper date index: {e}")
917
+ # Fallback: числовой индекс
918
+ return pd.RangeIndex(start=0, stop=steps)
919
+
920
+
921
+ def forecast_recursive(model, series, steps, freq='D'):
922
+ """Рекурсивная стратегия прогнозирования"""
923
+ predictions = []
924
+ current_series = series.copy()
925
+
926
+ for _ in range(steps):
927
+ if hasattr(model, 'predict'):
928
+ pred = model.predict(n_periods=1)
929
+ else:
930
+ pred = model.forecast(steps=1)
931
+ predictions.append(pred[0])
932
+ # Обновляем ряд для следующей итерации
933
+ current_series = pd.concat(
934
+ [current_series, pd.Series([pred[0]], index=[current_series.index[-1] + pd.Timedelta(days=1)])])
935
+
936
+ return np.array(predictions)
937
+
938
+
939
+ def forecast_direct(train_series, test_features, model_factory, steps):
940
+ """Прямая стратегия - отдельная модель для каждого горизонта"""
941
+ predictions = []
942
+ for h in range(1, steps + 1):
943
+ # Создаем смещенную целевую переменную
944
+ y_h = train_series.shift(-h).dropna()
945
+ X_h = train_series.iloc[:len(y_h)]
946
+
947
+ # Обучаем модель для горизонта h
948
+ model = model_factory()
949
+ model.fit(X_h.values.reshape(-1, 1), y_h.values)
950
+
951
+ # Прогноз для горизонта h
952
+ pred = model.predict(train_series.values[-1:].reshape(1, -1))
953
+ predictions.append(pred[0])
954
+
955
+ return np.array(predictions)
src/lab4_functions.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # lab4_functions.py
2
+ """
3
+ Простейший набор для ЛР №4:
4
+ - Feature engineering (lags, rolling)
5
+ - Обучение Ridge, Lasso, RandomForest, LightGBM (опционально)
6
+ - TimeSeriesSplit wrapper
7
+ """
8
+ import numpy as np
9
+ import pandas as pd
10
+ from typing import List, Dict
11
+ from sklearn.linear_model import Ridge, Lasso
12
+ from sklearn.ensemble import RandomForestRegressor
13
+ from sklearn.model_selection import TimeSeriesSplit, cross_val_score
14
+ from sklearn.metrics import mean_absolute_error
15
+ import warnings
16
+ warnings.filterwarnings("ignore")
17
+
18
+ try:
19
+ import lightgbm as lgb
20
+ LGB_AVAILABLE = True
21
+ except Exception:
22
+ LGB_AVAILABLE = False
23
+
24
+ def make_lag_features(df: pd.DataFrame, target:str, lags:List[int]=[1,7,30]):
25
+ dfc = df.copy().set_index('timestamp').sort_index()
26
+ for l in lags:
27
+ dfc[f'{target}_lag_{l}'] = dfc[target].shift(l)
28
+ dfc = dfc.dropna().reset_index()
29
+ return dfc
30
+
31
+ def train_baselines(X_train, y_train):
32
+ models = {}
33
+ models['Ridge'] = Ridge().fit(X_train, y_train)
34
+ models['Lasso'] = Lasso().fit(X_train, y_train)
35
+ models['RF'] = RandomForestRegressor(n_estimators=100, random_state=42).fit(X_train, y_train)
36
+ if LGB_AVAILABLE:
37
+ models['LightGBM'] = lgb.LGBMRegressor(n_estimators=100).fit(X_train, y_train)
38
+ return models
39
+
40
+ def cv_score_ts(model, X, y, n_splits=5, scoring='neg_mean_absolute_error'):
41
+ tscv = TimeSeriesSplit(n_splits=n_splits)
42
+ scores = cross_val_score(model, X, y, cv=tscv, scoring=scoring)
43
+ return scores.mean()
src/lab5_functions.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # lab5_functions.py
2
+ """
3
+ Минимальный LSTM example для ЛР №5 (PyTorch).
4
+ Если у вас нет torch — функции выбросят понятную ошибку.
5
+ """
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ try:
10
+ import torch
11
+ import torch.nn as nn
12
+ TORCH_AVAILABLE = True
13
+ except Exception:
14
+ TORCH_AVAILABLE = False
15
+
16
+ if TORCH_AVAILABLE:
17
+ class SimpleLSTM(nn.Module):
18
+ def __init__(self, input_size, hidden_size=64, num_layers=1, out_size=1):
19
+ super().__init__()
20
+ self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
21
+ self.fc = nn.Linear(hidden_size, out_size)
22
+ def forward(self, x):
23
+ out, _ = self.lstm(x)
24
+ return self.fc(out[:, -1, :])
25
+
26
+ def create_sequences(series, lookback=30, horizon=1):
27
+ X, y = [], []
28
+ for i in range(len(series) - lookback - horizon + 1):
29
+ X.append(series[i:i+lookback])
30
+ y.append(series[i+lookback:i+lookback+horizon])
31
+ return np.array(X), np.array(y)
32
+
33
+ def train_lstm(series, lookback=30, epochs=20, lr=1e-3):
34
+ X, y = create_sequences(series, lookback=lookback, horizon=1)
35
+ X = torch.from_numpy(X).float().unsqueeze(-1) # (N, L, 1)
36
+ y = torch.from_numpy(y).float().squeeze(-1)
37
+ model = SimpleLSTM(input_size=1)
38
+ opt = torch.optim.Adam(model.parameters(), lr=lr)
39
+ loss_fn = nn.MSELoss()
40
+ model.train()
41
+ for ep in range(epochs):
42
+ pred = model(X)
43
+ loss = loss_fn(pred.squeeze(), y)
44
+ opt.zero_grad(); loss.backward(); opt.step()
45
+ return model
46
+ else:
47
+ def train_lstm(*args, **kwargs):
48
+ raise ImportError("PyTorch не установлен. Установите torch, чтобы использовать LSTM.")
src/main.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ Главная страница приложения - навигация между лабораторными работами
3
+ Этот файл не используется, так как основное приложение находится в streamlit_app.py
4
+ """
5
+ # Этот файл оставлен для совместимости, но основное приложение находится в streamlit_app.py
6
+ # Для запуска используйте: streamlit run src/streamlit_app.py
7
+
src/russia_covid_dataset.csv ADDED
@@ -0,0 +1,821 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Date,DailyNewCases,ActiveCases,DailyNewDeaths
2
+ 2020-2-15,,0.0,
3
+ 2020-2-16,0.0,0.0,
4
+ 2020-2-17,0.0,0.0,
5
+ 2020-2-18,0.0,0.0,
6
+ 2020-2-19,0.0,0.0,
7
+ 2020-2-20,0.0,0.0,0.0
8
+ 2020-2-21,0.0,0.0,0.0
9
+ 2020-2-22,0.0,0.0,0.0
10
+ 2020-2-23,0.0,0.0,0.0
11
+ 2020-2-24,0.0,0.0,0.0
12
+ 2020-2-25,0.0,0.0,0.0
13
+ 2020-2-26,0.0,0.0,0.0
14
+ 2020-2-27,0.0,0.0,0.0
15
+ 2020-2-28,0.0,0.0,0.0
16
+ 2020-2-29,0.0,0.0,0.0
17
+ 2020-3-01,0.0,0.0,0.0
18
+ 2020-3-02,1.0,1.0,0.0
19
+ 2020-3-03,0.0,1.0,0.0
20
+ 2020-3-04,0.0,1.0,0.0
21
+ 2020-3-05,4.0,5.0,0.0
22
+ 2020-3-06,6.0,11.0,0.0
23
+ 2020-3-07,1.0,11.0,0.0
24
+ 2020-3-08,3.0,14.0,0.0
25
+ 2020-3-09,3.0,17.0,0.0
26
+ 2020-3-10,0.0,17.0,0.0
27
+ 2020-3-11,8.0,25.0,0.0
28
+ 2020-3-12,6.0,31.0,0.0
29
+ 2020-3-13,11.0,37.0,0.0
30
+ 2020-3-14,14.0,51.0,0.0
31
+ 2020-3-15,4.0,55.0,0.0
32
+ 2020-3-16,30.0,85.0,0.0
33
+ 2020-3-17,21.0,106.0,0.0
34
+ 2020-3-18,33.0,139.0,0.0
35
+ 2020-3-19,52.0,190.0,1.0
36
+ 2020-3-20,54.0,240.0,0.0
37
+ 2020-3-21,53.0,289.0,0.0
38
+ 2020-3-22,61.0,350.0,0.0
39
+ 2020-3-23,71.0,420.0,0.0
40
+ 2020-3-24,57.0,472.0,0.0
41
+ 2020-3-25,163.0,626.0,2.0
42
+ 2020-3-26,182.0,799.0,0.0
43
+ 2020-3-27,196.0,987.0,1.0
44
+ 2020-3-28,228.0,1211.0,0.0
45
+ 2020-3-29,270.0,1462.0,4.0
46
+ 2020-3-30,302.0,1761.0,1.0
47
+ 2020-3-31,501.0,2199.0,8.0
48
+ 2020-4-01,440.0,2563.0,7.0
49
+ 2020-4-02,771.0,3283.0,6.0
50
+ 2020-4-03,601.0,3834.0,4.0
51
+ 2020-4-04,582.0,4355.0,9.0
52
+ 2020-4-05,658.0,4989.0,2.0
53
+ 2020-4-06,954.0,5890.0,2.0
54
+ 2020-4-07,1154.0,6945.0,11.0
55
+ 2020-4-08,1175.0,8029.0,5.0
56
+ 2020-4-09,1459.0,9357.0,13.0
57
+ 2020-4-10,1786.0,11028.0,18.0
58
+ 2020-4-11,1667.0,12433.0,12.0
59
+ 2020-4-12,2186.0,14349.0,24.0
60
+ 2020-4-13,2558.0,16710.0,18.0
61
+ 2020-4-14,2774.0,19238.0,22.0
62
+ 2020-4-15,3388.0,22306.0,28.0
63
+ 2020-4-16,3448.0,25402.0,34.0
64
+ 2020-4-17,4070.0,29145.0,41.0
65
+ 2020-4-18,4785.0,33423.0,40.0
66
+ 2020-4-19,6060.0,39201.0,48.0
67
+ 2020-4-20,4268.0,43270.0,44.0
68
+ 2020-4-21,5642.0,48434.0,51.0
69
+ 2020-4-22,5236.0,53066.0,57.0
70
+ 2020-4-23,4774.0,57327.0,42.0
71
+ 2020-4-24,5849.0,62439.0,60.0
72
+ 2020-4-25,5966.0,67657.0,66.0
73
+ 2020-4-26,6361.0,73435.0,66.0
74
+ 2020-4-27,6198.0,79007.0,47.0
75
+ 2020-4-28,6411.0,84235.0,73.0
76
+ 2020-4-29,5841.0,88141.0,105.0
77
+ 2020-4-30,7099.0,93806.0,101.0
78
+ 2020-5-01,7933.0,100042.0,96.0
79
+ 2020-5-02,9623.0,107819.0,53.0
80
+ 2020-5-03,10633.0,116768.0,58.0
81
+ 2020-5-04,10581.0,125817.0,76.0
82
+ 2020-5-05,10102.0,134054.0,95.0
83
+ 2020-5-06,10559.0,143065.0,86.0
84
+ 2020-5-07,11231.0,151732.0,88.0
85
+ 2020-5-08,10699.0,159528.0,98.0
86
+ 2020-5-09,10817.0,164933.0,104.0
87
+ 2020-5-10,11012.0,173467.0,88.0
88
+ 2020-5-11,11656.0,179534.0,94.0
89
+ 2020-5-12,10899.0,186615.0,107.0
90
+ 2020-5-13,10028.0,192056.0,96.0
91
+ 2020-5-14,9974.0,196410.0,93.0
92
+ 2020-5-15,10598.0,202199.0,113.0
93
+ 2020-5-16,9200.0,206340.0,119.0
94
+ 2020-5-17,9709.0,211748.0,94.0
95
+ 2020-5-18,8926.0,217747.0,91.0
96
+ 2020-5-19,9263.0,220974.0,115.0
97
+ 2020-5-20,8764.0,220341.0,135.0
98
+ 2020-5-21,8849.0,221774.0,127.0
99
+ 2020-5-22,8894.0,223374.0,150.0
100
+ 2020-5-23,9434.0,224558.0,139.0
101
+ 2020-5-24,8599.0,227641.0,153.0
102
+ 2020-5-25,8946.0,230996.0,92.0
103
+ 2020-5-26,8915.0,227406.0,174.0
104
+ 2020-5-27,8338.0,224504.0,161.0
105
+ 2020-5-28,8371.0,223916.0,174.0
106
+ 2020-5-29,8572.0,223992.0,232.0
107
+ 2020-5-30,8952.0,224551.0,181.0
108
+ 2020-5-31,9268.0,229267.0,138.0
109
+ 2020-6-01,9035.0,234146.0,162.0
110
+ 2020-6-02,8863.0,231719.0,182.0
111
+ 2020-6-03,8536.0,231105.0,178.0
112
+ 2020-6-04,8831.0,231101.0,169.0
113
+ 2020-6-05,8726.0,231626.0,144.0
114
+ 2020-6-06,8855.0,231576.0,197.0
115
+ 2020-6-07,8984.0,235083.0,134.0
116
+ 2020-6-08,8985.0,239999.0,112.0
117
+ 2020-6-09,8595.0,236714.0,171.0
118
+ 2020-6-10,8404.0,234516.0,216.0
119
+ 2020-6-11,8779.0,234754.0,174.0
120
+ 2020-6-12,8987.0,235338.0,183.0
121
+ 2020-6-13,8706.0,238659.0,114.0
122
+ 2020-6-14,8835.0,241966.0,119.0
123
+ 2020-6-15,8246.0,245580.0,143.0
124
+ 2020-6-16,8248.0,243868.0,193.0
125
+ 2020-6-17,7843.0,241481.0,194.0
126
+ 2020-6-18,7790.0,239468.0,182.0
127
+ 2020-6-19,7972.0,236816.0,181.0
128
+ 2020-6-20,7889.0,234358.0,161.0
129
+ 2020-6-21,7728.0,236858.0,109.0
130
+ 2020-6-22,7600.0,239658.0,95.0
131
+ 2020-6-23,7425.0,234917.0,153.0
132
+ 2020-6-24,7176.0,229546.0,154.0
133
+ 2020-6-25,7113.0,230225.0,92.0
134
+ 2020-6-26,6800.0,227861.0,176.0
135
+ 2020-6-27,6852.0,225325.0,188.0
136
+ 2020-6-28,6791.0,226277.0,104.0
137
+ 2020-6-29,6719.0,228560.0,93.0
138
+ 2020-6-30,6693.0,225879.0,154.0
139
+ 2020-7-01,6556.0,221938.0,216.0
140
+ 2020-7-02,6760.0,222504.0,147.0
141
+ 2020-7-03,6718.0,220131.0,176.0
142
+ 2020-7-04,6632.0,217609.0,168.0
143
+ 2020-7-05,6736.0,220340.0,134.0
144
+ 2020-7-06,6611.0,223237.0,135.0
145
+ 2020-7-07,6368.0,219856.0,198.0
146
+ 2020-7-08,6562.0,217614.0,173.0
147
+ 2020-7-09,6509.0,215142.0,176.0
148
+ 2020-7-10,6635.0,213851.0,174.0
149
+ 2020-7-11,6611.0,211896.0,188.0
150
+ 2020-7-12,6615.0,214766.0,130.0
151
+ 2020-7-13,6537.0,218239.0,104.0
152
+ 2020-7-14,6248.0,215508.0,175.0
153
+ 2020-7-15,6422.0,211350.0,156.0
154
+ 2020-7-16,6428.0,209168.0,167.0
155
+ 2020-7-17,6406.0,207707.0,186.0
156
+ 2020-7-18,6234.0,206327.0,124.0
157
+ 2020-7-19,6109.0,208860.0,95.0
158
+ 2020-7-20,5940.0,211457.0,85.0
159
+ 2020-7-21,5842.0,208364.0,153.0
160
+ 2020-7-22,5862.0,204392.0,165.0
161
+ 2020-7-23,5848.0,201816.0,147.0
162
+ 2020-7-24,5811.0,199029.0,154.0
163
+ 2020-7-25,5871.0,196388.0,146.0
164
+ 2020-7-26,5765.0,198966.0,77.0
165
+ 2020-7-27,5635.0,201437.0,85.0
166
+ 2020-7-28,5395.0,197794.0,150.0
167
+ 2020-7-29,5475.0,194984.0,169.0
168
+ 2020-7-30,5509.0,191042.0,129.0
169
+ 2020-7-31,5482.0,187608.0,161.0
170
+ 2020-8-01,5462.0,184861.0,95.0
171
+ 2020-8-02,5427.0,186569.0,70.0
172
+ 2020-8-03,5394.0,188464.0,79.0
173
+ 2020-8-04,5159.0,185601.0,144.0
174
+ 2020-8-05,5204.0,183111.0,139.0
175
+ 2020-8-06,5267.0,180931.0,116.0
176
+ 2020-8-07,5241.0,178818.0,119.0
177
+ 2020-8-08,5212.0,177286.0,129.0
178
+ 2020-8-09,5189.0,179183.0,77.0
179
+ 2020-8-10,5118.0,180972.0,70.0
180
+ 2020-8-11,4945.0,179293.0,130.0
181
+ 2020-8-12,5102.0,177143.0,129.0
182
+ 2020-8-13,5057.0,175978.0,124.0
183
+ 2020-8-14,5065.0,174361.0,114.0
184
+ 2020-8-15,5061.0,172856.0,119.0
185
+ 2020-8-16,4969.0,174200.0,68.0
186
+ 2020-8-17,4892.0,175904.0,55.0
187
+ 2020-8-18,4748.0,173993.0,132.0
188
+ 2020-8-19,4828.0,171909.0,117.0
189
+ 2020-8-20,4785.0,170494.0,110.0
190
+ 2020-8-21,4870.0,169457.0,90.0
191
+ 2020-8-22,4921.0,168110.0,121.0
192
+ 2020-8-23,4852.0,169727.0,73.0
193
+ 2020-8-24,4744.0,171950.0,65.0
194
+ 2020-8-25,4696.0,169874.0,120.0
195
+ 2020-8-26,4676.0,168032.0,115.0
196
+ 2020-8-27,4711.0,166211.0,121.0
197
+ 2020-8-28,4829.0,165025.0,110.0
198
+ 2020-8-29,4941.0,163938.0,111.0
199
+ 2020-8-30,4980.0,166251.0,68.0
200
+ 2020-8-31,4993.0,168756.0,83.0
201
+ 2020-9-01,4729.0,167044.0,123.0
202
+ 2020-9-02,4952.0,166417.0,115.0
203
+ 2020-9-03,4995.0,165532.0,114.0
204
+ 2020-9-04,5110.0,164709.0,121.0
205
+ 2020-9-05,5205.0,164425.0,110.0
206
+ 2020-9-06,5195.0,166736.0,61.0
207
+ 2020-9-07,5185.0,169542.0,51.0
208
+ 2020-9-08,5099.0,167747.0,122.0
209
+ 2020-9-09,5218.0,166414.0,142.0
210
+ 2020-9-10,5363.0,165734.0,128.0
211
+ 2020-9-11,5504.0,165402.0,102.0
212
+ 2020-9-12,5488.0,165343.0,119.0
213
+ 2020-9-13,5449.0,168008.0,94.0
214
+ 2020-9-14,5509.0,170985.0,57.0
215
+ 2020-9-15,5529.0,170759.0,150.0
216
+ 2020-9-16,5670.0,170488.0,132.0
217
+ 2020-9-17,5762.0,170352.0,144.0
218
+ 2020-9-18,5905.0,170784.0,134.0
219
+ 2020-9-19,6065.0,171450.0,144.0
220
+ 2020-9-20,6148.0,174624.0,79.0
221
+ 2020-9-21,6196.0,178133.0,71.0
222
+ 2020-9-22,6215.0,178212.0,160.0
223
+ 2020-9-23,6431.0,178743.0,150.0
224
+ 2020-9-24,6595.0,179059.0,149.0
225
+ 2020-9-25,7212.0,181846.0,108.0
226
+ 2020-9-26,7523.0,183196.0,169.0
227
+ 2020-9-27,7867.0,187896.0,99.0
228
+ 2020-9-28,8135.0,193268.0,61.0
229
+ 2020-9-29,8232.0,194861.0,160.0
230
+ 2020-9-30,8481.0,197307.0,177.0
231
+ 2020-10-01,8945.0,200098.0,169.0
232
+ 2020-10-02,9412.0,203270.0,186.0
233
+ 2020-10-03,9859.0,207392.0,174.0
234
+ 2020-10-04,10499.0,214500.0,107.0
235
+ 2020-10-05,10888.0,222090.0,117.0
236
+ 2020-10-06,11615.0,227265.0,188.0
237
+ 2020-10-07,11115.0,231479.0,202.0
238
+ 2020-10-08,11493.0,235727.0,191.0
239
+ 2020-10-09,12126.0,240560.0,201.0
240
+ 2020-10-10,12846.0,246428.0,197.0
241
+ 2020-10-11,13634.0,255679.0,143.0
242
+ 2020-10-12,13592.0,265353.0,125.0
243
+ 2020-10-13,13868.0,271427.0,244.0
244
+ 2020-10-14,14231.0,277499.0,239.0
245
+ 2020-10-15,13754.0,282575.0,286.0
246
+ 2020-10-16,15150.0,289008.0,232.0
247
+ 2020-10-17,14922.0,295034.0,279.0
248
+ 2020-10-18,15099.0,304571.0,185.0
249
+ 2020-10-19,15982.0,315046.0,179.0
250
+ 2020-10-20,16319.0,321392.0,269.0
251
+ 2020-10-21,15700.0,325823.0,317.0
252
+ 2020-10-22,15971.0,330076.0,290.0
253
+ 2020-10-23,17340.0,335870.0,283.0
254
+ 2020-10-24,16521.0,340528.0,296.0
255
+ 2020-10-25,16710.0,349305.0,229.0
256
+ 2020-10-26,17347.0,358859.0,219.0
257
+ 2020-10-27,16550.0,362245.0,320.0
258
+ 2020-10-28,16202.0,365740.0,346.0
259
+ 2020-10-29,17717.0,368351.0,366.0
260
+ 2020-10-30,18283.0,371760.0,355.0
261
+ 2020-10-31,18140.0,374712.0,334.0
262
+ 2020-11-01,18665.0,382873.0,245.0
263
+ 2020-11-02,18257.0,390532.0,238.0
264
+ 2020-11-03,18648.0,393494.0,355.0
265
+ 2020-11-04,19768.0,397306.0,389.0
266
+ 2020-11-05,19404.0,404180.0,292.0
267
+ 2020-11-06,20582.0,407429.0,378.0
268
+ 2020-11-07,20396.0,410658.0,364.0
269
+ 2020-11-08,20498.0,419378.0,286.0
270
+ 2020-11-09,21798.0,430198.0,256.0
271
+ 2020-11-10,20977.0,435207.0,368.0
272
+ 2020-11-11,19851.0,436010.0,432.0
273
+ 2020-11-12,21608.0,438368.0,439.0
274
+ 2020-11-13,21983.0,441205.0,411.0
275
+ 2020-11-14,22702.0,444890.0,391.0
276
+ 2020-11-15,22572.0,452654.0,352.0
277
+ 2020-11-16,22778.0,461265.0,303.0
278
+ 2020-11-17,22410.0,461178.0,442.0
279
+ 2020-11-18,20985.0,456528.0,456.0
280
+ 2020-11-19,23610.0,454102.0,463.0
281
+ 2020-11-20,24318.0,453201.0,461.0
282
+ 2020-11-21,24822.0,451535.0,467.0
283
+ 2020-11-22,24581.0,457707.0,401.0
284
+ 2020-11-23,25173.0,466517.0,361.0
285
+ 2020-11-24,24326.0,467126.0,491.0
286
+ 2020-11-25,23675.0,464546.0,507.0
287
+ 2020-11-26,25487.0,464436.0,524.0
288
+ 2020-11-27,27543.0,464801.0,496.0
289
+ 2020-11-28,27100.0,464095.0,510.0
290
+ 2020-11-29,26683.0,468332.0,459.0
291
+ 2020-11-30,26338.0,477055.0,368.0
292
+ 2020-12-01,26402.0,478125.0,569.0
293
+ 2020-12-02,25345.0,475999.0,589.0
294
+ 2020-12-03,28145.0,474088.0,554.0
295
+ 2020-12-04,27403.0,472021.0,569.0
296
+ 2020-12-05,28782.0,472651.0,508.0
297
+ 2020-12-06,29039.0,479891.0,457.0
298
+ 2020-12-07,28142.0,488727.0,456.0
299
+ 2020-12-08,26097.0,489324.0,562.0
300
+ 2020-12-09,26190.0,488689.0,559.0
301
+ 2020-12-10,27927.0,490177.0,562.0
302
+ 2020-12-11,28585.0,491978.0,613.0
303
+ 2020-12-12,28137.0,493437.0,560.0
304
+ 2020-12-13,28080.0,500752.0,488.0
305
+ 2020-12-14,27328.0,509068.0,450.0
306
+ 2020-12-15,26689.0,510367.0,577.0
307
+ 2020-12-16,26509.0,509790.0,596.0
308
+ 2020-12-17,28214.0,510977.0,587.0
309
+ 2020-12-18,28552.0,512825.0,611.0
310
+ 2020-12-19,28209.0,514340.0,585.0
311
+ 2020-12-20,28948.0,521862.0,511.0
312
+ 2020-12-21,29350.0,531014.0,493.0
313
+ 2020-12-22,28776.0,535071.0,561.0
314
+ 2020-12-23,27250.0,537325.0,549.0
315
+ 2020-12-24,29935.0,539735.0,635.0
316
+ 2020-12-25,29018.0,540793.0,563.0
317
+ 2020-12-26,29258.0,541299.0,567.0
318
+ 2020-12-27,28284.0,544641.0,552.0
319
+ 2020-12-28,27787.0,551461.0,487.0
320
+ 2020-12-29,27002.0,553027.0,562.0
321
+ 2020-12-30,26513.0,549706.0,599.0
322
+ 2020-12-31,27747.0,547938.0,593.0
323
+ 2021-1-01,27039.0,548643.0,536.0
324
+ 2021-1-02,26301.0,555600.0,447.0
325
+ 2021-1-03,24150.0,559399.0,504.0
326
+ 2021-1-04,23351.0,561114.0,482.0
327
+ 2021-1-05,24246.0,562210.0,518.0
328
+ 2021-1-06,24217.0,562927.0,445.0
329
+ 2021-1-07,23541.0,562233.0,506.0
330
+ 2021-1-08,23652.0,563754.0,454.0
331
+ 2021-1-09,23309.0,562913.0,470.0
332
+ 2021-1-10,22851.0,561228.0,456.0
333
+ 2021-1-11,23315.0,562321.0,436.0
334
+ 2021-1-12,22934.0,559969.0,531.0
335
+ 2021-1-13,22850.0,553595.0,566.0
336
+ 2021-1-14,24763.0,549832.0,570.0
337
+ 2021-1-15,24715.0,546356.0,555.0
338
+ 2021-1-16,24092.0,542547.0,590.0
339
+ 2021-1-17,23586.0,542212.0,481.0
340
+ 2021-1-18,22857.0,546265.0,471.0
341
+ 2021-1-19,21734.0,544151.0,586.0
342
+ 2021-1-20,21152.0,539416.0,597.0
343
+ 2021-1-21,21887.0,533789.0,612.0
344
+ 2021-1-22,21513.0,527404.0,580.0
345
+ 2021-1-23,20921.0,519987.0,559.0
346
+ 2021-1-24,21127.0,518178.0,491.0
347
+ 2021-1-25,19290.0,518009.0,456.0
348
+ 2021-1-26,18241.0,511888.0,564.0
349
+ 2021-1-27,17741.0,501113.0,594.0
350
+ 2021-1-28,19138.0,492901.0,575.0
351
+ 2021-1-29,19238.0,485401.0,534.0
352
+ 2021-1-30,19032.0,479419.0,512.0
353
+ 2021-1-31,18359.0,477253.0,485.0
354
+ 2021-2-01,17648.0,476295.0,437.0
355
+ 2021-2-02,16643.0,470027.0,539.0
356
+ 2021-2-03,16474.0,461153.0,526.0
357
+ 2021-2-04,16714.0,452800.0,521.0
358
+ 2021-2-05,16688.0,445379.0,527.0
359
+ 2021-2-06,16627.0,438678.0,497.0
360
+ 2021-2-07,16048.0,434410.0,432.0
361
+ 2021-2-08,15916.0,434038.0,407.0
362
+ 2021-2-09,15019.0,426732.0,530.0
363
+ 2021-2-10,14494.0,418115.0,536.0
364
+ 2021-2-11,15038.0,410639.0,553.0
365
+ 2021-2-12,15089.0,404501.0,507.0
366
+ 2021-2-13,14861.0,400095.0,502.0
367
+ 2021-2-14,14185.0,398656.0,430.0
368
+ 2021-2-15,14207.0,398534.0,394.0
369
+ 2021-2-16,13233.0,393681.0,459.0
370
+ 2021-2-17,12828.0,388123.0,467.0
371
+ 2021-2-18,13447.0,382360.0,480.0
372
+ 2021-2-19,13433.0,376686.0,470.0
373
+ 2021-2-20,12953.0,371675.0,480.0
374
+ 2021-2-21,12742.0,367988.0,417.0
375
+ 2021-2-22,12604.0,367312.0,337.0
376
+ 2021-2-23,11823.0,365762.0,417.0
377
+ 2021-2-24,11749.0,364910.0,383.0
378
+ 2021-2-25,11198.0,359560.0,446.0
379
+ 2021-2-26,11086.0,354496.0,428.0
380
+ 2021-2-27,11534.0,349571.0,439.0
381
+ 2021-2-28,11359.0,348160.0,379.0
382
+ 2021-3-01,11571.0,348121.0,333.0
383
+ 2021-3-02,10565.0,343279.0,441.0
384
+ 2021-3-03,10535.0,337668.0,452.0
385
+ 2021-3-04,11385.0,332455.0,475.0
386
+ 2021-3-05,11024.0,327553.0,462.0
387
+ 2021-3-06,11022.0,323107.0,441.0
388
+ 2021-3-07,10595.0,321758.0,368.0
389
+ 2021-3-08,10253.0,321310.0,379.0
390
+ 2021-3-09,9445.0,320488.0,336.0
391
+ 2021-3-10,9079.0,315751.0,466.0
392
+ 2021-3-11,9270.0,310556.0,459.0
393
+ 2021-3-12,9794.0,306368.0,486.0
394
+ 2021-3-13,9908.0,302933.0,475.0
395
+ 2021-3-14,10083.0,303209.0,395.0
396
+ 2021-3-15,9437.0,303975.0,404.0
397
+ 2021-3-16,9393.0,302281.0,443.0
398
+ 2021-3-17,8998.0,300097.0,427.0
399
+ 2021-3-18,9803.0,297379.0,460.0
400
+ 2021-3-19,9699.0,294298.0,443.0
401
+ 2021-3-20,9632.0,292259.0,392.0
402
+ 2021-3-21,9299.0,292444.0,371.0
403
+ 2021-3-22,9284.0,293577.0,361.0
404
+ 2021-3-23,8457.0,290747.0,427.0
405
+ 2021-3-24,8861.0,288852.0,401.0
406
+ 2021-3-25,9221.0,286799.0,393.0
407
+ 2021-3-26,9167.0,284681.0,405.0
408
+ 2021-3-27,8885.0,282842.0,387.0
409
+ 2021-3-28,9088.0,282964.0,336.0
410
+ 2021-3-29,8711.0,284102.0,293.0
411
+ 2021-3-30,8277.0,282382.0,409.0
412
+ 2021-3-31,8275.0,280073.0,408.0
413
+ 2021-4-01,9169.0,278612.0,383.0
414
+ 2021-4-02,8792.0,277172.0,400.0
415
+ 2021-4-03,9021.0,276191.0,384.0
416
+ 2021-4-04,8817.0,276439.0,357.0
417
+ 2021-4-05,8646.0,277690.0,343.0
418
+ 2021-4-06,8328.0,276727.0,389.0
419
+ 2021-4-07,8294.0,275202.0,374.0
420
+ 2021-4-08,8672.0,273951.0,365.0
421
+ 2021-4-09,9150.0,273037.0,402.0
422
+ 2021-4-10,8704.0,271760.0,402.0
423
+ 2021-4-11,8702.0,272895.0,337.0
424
+ 2021-4-12,8320.0,274282.0,277.0
425
+ 2021-4-13,8173.0,272506.0,338.0
426
+ 2021-4-14,8326.0,270986.0,399.0
427
+ 2021-4-15,8944.0,269307.0,398.0
428
+ 2021-4-16,8995.0,268796.0,397.0
429
+ 2021-4-17,9321.0,268887.0,398.0
430
+ 2021-4-18,8632.0,269739.0,389.0
431
+ 2021-4-19,8589.0,271164.0,346.0
432
+ 2021-4-20,8164.0,269318.0,379.0
433
+ 2021-4-21,8271.0,267546.0,399.0
434
+ 2021-4-22,8996.0,267211.0,397.0
435
+ 2021-4-23,8840.0,266246.0,398.0
436
+ 2021-4-24,8828.0,265421.0,399.0
437
+ 2021-4-25,8780.0,266329.0,332.0
438
+ 2021-4-26,8803.0,268145.0,356.0
439
+ 2021-4-27,8053.0,267767.0,392.0
440
+ 2021-4-28,7848.0,266808.0,387.0
441
+ 2021-4-29,9284.0,267286.0,364.0
442
+ 2021-4-30,8731.0,267214.0,397.0
443
+ 2021-5-01,9270.0,267455.0,392.0
444
+ 2021-5-02,8697.0,268471.0,342.0
445
+ 2021-5-03,8489.0,270257.0,336.0
446
+ 2021-5-04,7770.0,270935.0,337.0
447
+ 2021-5-05,7975.0,271044.0,360.0
448
+ 2021-5-06,7639.0,270544.0,351.0
449
+ 2021-5-07,8386.0,270532.0,376.0
450
+ 2021-5-08,8329.0,270236.0,370.0
451
+ 2021-5-09,8419.0,270804.0,334.0
452
+ 2021-5-10,8465.0,272174.0,321.0
453
+ 2021-5-11,8115.0,272951.0,329.0
454
+ 2021-5-12,8217.0,272199.0,355.0
455
+ 2021-5-13,8380.0,270838.0,392.0
456
+ 2021-5-14,9462.0,270151.0,393.0
457
+ 2021-5-15,8790.0,268711.0,364.0
458
+ 2021-5-16,8554.0,268301.0,391.0
459
+ 2021-5-17,9328.0,270108.0,340.0
460
+ 2021-5-18,8183.0,268955.0,364.0
461
+ 2021-5-19,7920.0,266924.0,390.0
462
+ 2021-5-20,9232.0,265777.0,396.0
463
+ 2021-5-21,8937.0,264986.0,378.0
464
+ 2021-5-22,8709.0,263964.0,386.0
465
+ 2021-5-23,8951.0,265261.0,357.0
466
+ 2021-5-24,8406.0,266898.0,319.0
467
+ 2021-5-25,7884.0,265646.0,393.0
468
+ 2021-5-26,8373.0,264478.0,406.0
469
+ 2021-5-27,9039.0,263356.0,402.0
470
+ 2021-5-28,9252.0,262819.0,404.0
471
+ 2021-5-29,9289.0,262457.0,401.0
472
+ 2021-5-30,9694.0,264410.0,355.0
473
+ 2021-5-31,8475.0,265831.0,339.0
474
+ 2021-6-01,9500.0,265965.0,372.0
475
+ 2021-6-02,8832.0,265383.0,394.0
476
+ 2021-6-03,8933.0,264540.0,393.0
477
+ 2021-6-04,8947.0,264580.0,377.0
478
+ 2021-6-05,9145.0,264761.0,399.0
479
+ 2021-6-06,9163.0,266204.0,351.0
480
+ 2021-6-07,9429.0,268547.0,330.0
481
+ 2021-6-08,9977.0,269262.0,379.0
482
+ 2021-6-09,10407.0,269456.0,399.0
483
+ 2021-6-10,11699.0,270676.0,383.0
484
+ 2021-6-11,12505.0,272597.0,396.0
485
+ 2021-6-12,13510.0,275722.0,399.0
486
+ 2021-6-13,14723.0,280922.0,357.0
487
+ 2021-6-14,13721.0,285960.0,371.0
488
+ 2021-6-15,14185.0,291169.0,379.0
489
+ 2021-6-16,13397.0,293914.0,396.0
490
+ 2021-6-17,14057.0,296350.0,416.0
491
+ 2021-6-18,17262.0,302205.0,453.0
492
+ 2021-6-19,17906.0,308961.0,466.0
493
+ 2021-6-20,17611.0,317493.0,450.0
494
+ 2021-6-21,17378.0,326070.0,440.0
495
+ 2021-6-22,16715.0,331122.0,546.0
496
+ 2021-6-23,17594.0,335508.0,548.0
497
+ 2021-6-24,20182.0,341617.0,568.0
498
+ 2021-6-25,20393.0,347385.0,601.0
499
+ 2021-6-26,21665.0,354084.0,619.0
500
+ 2021-6-27,20538.0,361295.0,599.0
501
+ 2021-6-28,21650.0,369708.0,611.0
502
+ 2021-6-29,20616.0,374975.0,652.0
503
+ 2021-6-30,21042.0,378992.0,669.0
504
+ 2021-7-01,23543.0,384935.0,672.0
505
+ 2021-7-02,23218.0,389277.0,679.0
506
+ 2021-7-03,24439.0,395120.0,697.0
507
+ 2021-7-04,25142.0,404115.0,663.0
508
+ 2021-7-05,24353.0,413274.0,654.0
509
+ 2021-7-06,23378.0,417504.0,737.0
510
+ 2021-7-07,23962.0,420674.0,725.0
511
+ 2021-7-08,24818.0,423422.0,734.0
512
+ 2021-7-09,25766.0,426630.0,726.0
513
+ 2021-7-10,25082.0,433210.0,752.0
514
+ 2021-7-11,25033.0,440112.0,749.0
515
+ 2021-7-12,25140.0,448113.0,710.0
516
+ 2021-7-13,24702.0,452469.0,780.0
517
+ 2021-7-14,23827.0,454241.0,786.0
518
+ 2021-7-15,25293.0,457250.0,791.0
519
+ 2021-7-16,25704.0,460223.0,799.0
520
+ 2021-7-17,25116.0,463115.0,787.0
521
+ 2021-7-18,25018.0,468483.0,764.0
522
+ 2021-7-19,24633.0,473633.0,719.0
523
+ 2021-7-20,23770.0,474401.0,784.0
524
+ 2021-7-21,23704.0,474738.0,783.0
525
+ 2021-7-22,24471.0,475753.0,796.0
526
+ 2021-7-23,23811.0,476222.0,795.0
527
+ 2021-7-24,23947.0,477418.0,799.0
528
+ 2021-7-25,24072.0,482033.0,779.0
529
+ 2021-7-26,23239.0,488345.0,727.0
530
+ 2021-7-27,23032.0,490482.0,779.0
531
+ 2021-7-28,22420.0,491525.0,798.0
532
+ 2021-7-29,23270.0,493162.0,799.0
533
+ 2021-7-30,23564.0,495447.0,794.0
534
+ 2021-7-31,23807.0,498691.0,792.0
535
+ 2021-8-01,22804.0,503435.0,789.0
536
+ 2021-8-02,23508.0,511265.0,785.0
537
+ 2021-8-03,22010.0,513524.0,788.0
538
+ 2021-8-04,22589.0,515227.0,790.0
539
+ 2021-8-05,23120.0,517183.0,794.0
540
+ 2021-8-06,22660.0,518910.0,792.0
541
+ 2021-8-07,22320.0,520952.0,793.0
542
+ 2021-8-08,22866.0,527362.0,787.0
543
+ 2021-8-09,22160.0,534279.0,769.0
544
+ 2021-8-10,21378.0,536136.0,792.0
545
+ 2021-8-11,21571.0,536841.0,799.0
546
+ 2021-8-12,21932.0,537770.0,808.0
547
+ 2021-8-13,22277.0,539864.0,815.0
548
+ 2021-8-14,22144.0,541639.0,819.0
549
+ 2021-8-15,21624.0,546021.0,816.0
550
+ 2021-8-16,20765.0,550379.0,806.0
551
+ 2021-8-17,20958.0,552125.0,805.0
552
+ 2021-8-18,20914.0,551527.0,799.0
553
+ 2021-8-19,21058.0,547777.0,791.0
554
+ 2021-8-20,20992.0,547633.0,785.0
555
+ 2021-8-21,21000.0,547189.0,797.0
556
+ 2021-8-22,20564.0,551577.0,762.0
557
+ 2021-8-23,19454.0,554854.0,776.0
558
+ 2021-8-24,18833.0,554257.0,794.0
559
+ 2021-8-25,19536.0,553330.0,809.0
560
+ 2021-8-26,19630.0,552479.0,820.0
561
+ 2021-8-27,19509.0,551973.0,798.0
562
+ 2021-8-28,19492.0,551255.0,799.0
563
+ 2021-8-29,19286.0,552940.0,797.0
564
+ 2021-8-30,18325.0,556293.0,792.0
565
+ 2021-8-31,17813.0,554687.0,795.0
566
+ 2021-9-01,18368.0,553940.0,790.0
567
+ 2021-9-02,18985.0,553458.0,798.0
568
+ 2021-9-03,18856.0,552825.0,799.0
569
+ 2021-9-04,18780.0,552072.0,796.0
570
+ 2021-9-05,18645.0,554668.0,793.0
571
+ 2021-9-06,17856.0,557458.0,790.0
572
+ 2021-9-07,17425.0,556845.0,795.0
573
+ 2021-9-08,18024.0,555810.0,797.0
574
+ 2021-9-09,18380.0,553757.0,794.0
575
+ 2021-9-10,18341.0,554188.0,789.0
576
+ 2021-9-11,18891.0,554395.0,796.0
577
+ 2021-9-12,18554.0,557664.0,788.0
578
+ 2021-9-13,18178.0,562654.0,719.0
579
+ 2021-9-14,17837.0,563803.0,781.0
580
+ 2021-9-15,18841.0,564813.0,792.0
581
+ 2021-9-16,19594.0,566287.0,794.0
582
+ 2021-9-17,19905.0,568782.0,791.0
583
+ 2021-9-18,20329.0,572065.0,799.0
584
+ 2021-9-19,20174.0,578028.0,793.0
585
+ 2021-9-20,19744.0,585002.0,778.0
586
+ 2021-9-21,19179.0,587932.0,812.0
587
+ 2021-9-22,19706.0,590719.0,817.0
588
+ 2021-9-23,21438.0,594770.0,820.0
589
+ 2021-9-24,21379.0,599493.0,828.0
590
+ 2021-9-25,22041.0,604387.0,822.0
591
+ 2021-9-26,22498.0,612409.0,805.0
592
+ 2021-9-27,22236.0,620353.0,779.0
593
+ 2021-9-28,21559.0,623692.0,852.0
594
+ 2021-9-29,22430.0,626809.0,857.0
595
+ 2021-9-30,23888.0,631004.0,867.0
596
+ 2021-10-01,24522.0,634684.0,887.0
597
+ 2021-10-02,25219.0,641165.0,886.0
598
+ 2021-10-03,25769.0,650653.0,890.0
599
+ 2021-10-04,25781.0,661025.0,883.0
600
+ 2021-10-05,25110.0,666672.0,895.0
601
+ 2021-10-06,25133.0,671035.0,929.0
602
+ 2021-10-07,27550.0,677331.0,924.0
603
+ 2021-10-08,27246.0,683075.0,936.0
604
+ 2021-10-09,29362.0,690420.0,968.0
605
+ 2021-10-10,28647.0,700831.0,962.0
606
+ 2021-10-11,29409.0,713823.0,957.0
607
+ 2021-10-12,28190.0,720334.0,973.0
608
+ 2021-10-13,28717.0,726266.0,984.0
609
+ 2021-10-14,31299.0,734909.0,986.0
610
+ 2021-10-15,32196.0,743839.0,998.0
611
+ 2021-10-16,33208.0,754162.0,1002.0
612
+ 2021-10-17,34303.0,768751.0,997.0
613
+ 2021-10-18,34325.0,785647.0,998.0
614
+ 2021-10-19,33740.0,794946.0,1015.0
615
+ 2021-10-20,34073.0,802760.0,1028.0
616
+ 2021-10-21,36339.0,812168.0,1036.0
617
+ 2021-10-22,37141.0,822792.0,1064.0
618
+ 2021-10-23,37678.0,833318.0,1075.0
619
+ 2021-10-24,35660.0,845122.0,1072.0
620
+ 2021-10-25,37930.0,861293.0,1069.0
621
+ 2021-10-26,36446.0,869660.0,1106.0
622
+ 2021-10-27,36582.0,875968.0,1123.0
623
+ 2021-10-28,40096.0,885587.0,1159.0
624
+ 2021-10-29,39849.0,893811.0,1163.0
625
+ 2021-10-30,40251.0,903993.0,1160.0
626
+ 2021-10-31,40993.0,916713.0,1158.0
627
+ 2021-11-01,40402.0,932773.0,1155.0
628
+ 2021-11-02,39008.0,939698.0,1178.0
629
+ 2021-11-03,40443.0,946145.0,1189.0
630
+ 2021-11-04,40217.0,953239.0,1195.0
631
+ 2021-11-05,40735.0,964177.0,1192.0
632
+ 2021-11-06,41335.0,975123.0,1188.0
633
+ 2021-11-07,39165.0,986303.0,1179.0
634
+ 2021-11-08,39400.0,998931.0,1190.0
635
+ 2021-11-09,39160.0,1004844.0,1211.0
636
+ 2021-11-10,38058.0,1007098.0,1239.0
637
+ 2021-11-11,40759.0,1013464.0,1237.0
638
+ 2021-11-12,40123.0,1018707.0,1235.0
639
+ 2021-11-13,39256.0,1022920.0,1241.0
640
+ 2021-11-14,38823.0,1030703.0,1219.0
641
+ 2021-11-15,38420.0,1040210.0,1211.0
642
+ 2021-11-16,36818.0,1041627.0,1240.0
643
+ 2021-11-17,36626.0,1040618.0,1247.0
644
+ 2021-11-18,37374.0,1040327.0,1251.0
645
+ 2021-11-19,37156.0,1039225.0,1254.0
646
+ 2021-11-20,37120.0,1038919.0,1254.0
647
+ 2021-11-21,36970.0,1042133.0,1252.0
648
+ 2021-11-22,35681.0,1047860.0,1241.0
649
+ 2021-11-23,33996.0,1044562.0,1243.0
650
+ 2021-11-24,33558.0,1040198.0,1240.0
651
+ 2021-11-25,33796.0,1034306.0,1238.0
652
+ 2021-11-26,34690.0,1031616.0,1235.0
653
+ 2021-11-27,33946.0,1027829.0,1239.0
654
+ 2021-11-28,33548.0,1029507.0,1224.0
655
+ 2021-11-29,33860.0,1034458.0,1209.0
656
+ 2021-11-30,32648.0,1032435.0,1229.0
657
+ 2021-12-01,32837.0,1028367.0,1226.0
658
+ 2021-12-02,33389.0,1025350.0,1221.0
659
+ 2021-12-03,32930.0,1020549.0,1217.0
660
+ 2021-12-04,32974.0,1017126.0,1215.0
661
+ 2021-12-05,32602.0,1017929.0,1206.0
662
+ 2021-12-06,32136.0,1020811.0,1184.0
663
+ 2021-12-07,31096.0,1016110.0,1182.0
664
+ 2021-12-08,30752.0,1008707.0,1179.0
665
+ 2021-12-09,30209.0,1001941.0,1181.0
666
+ 2021-12-10,30873.0,995981.0,1176.0
667
+ 2021-12-11,30288.0,988652.0,1171.0
668
+ 2021-12-12,29929.0,986058.0,1132.0
669
+ 2021-12-13,29558.0,985934.0,1121.0
670
+ 2021-12-14,28343.0,979048.0,1145.0
671
+ 2021-12-15,28363.0,970636.0,1142.0
672
+ 2021-12-16,28486.0,960834.0,1133.0
673
+ 2021-12-17,27743.0,950060.0,1080.0
674
+ 2021-12-18,27434.0,938377.0,1076.0
675
+ 2021-12-19,27967.0,932666.0,1023.0
676
+ 2021-12-20,27022.0,928610.0,1019.0
677
+ 2021-12-21,25907.0,913271.0,1027.0
678
+ 2021-12-22,25264.0,895193.0,1020.0
679
+ 2021-12-23,25667.0,878213.0,1002.0
680
+ 2021-12-24,24703.0,860705.0,998.0
681
+ 2021-12-25,24946.0,842563.0,981.0
682
+ 2021-12-26,23721.0,828031.0,968.0
683
+ 2021-12-27,23210.0,816589.0,937.0
684
+ 2021-12-28,21922.0,793615.0,935.0
685
+ 2021-12-29,21119.0,771026.0,932.0
686
+ 2021-12-30,21073.0,748169.0,926.0
687
+ 2021-12-31,20638.0,727203.0,912.0
688
+ 2022-1-01,19751.0,712963.0,847.0
689
+ 2022-1-02,18233.0,703409.0,811.0
690
+ 2022-1-03,16343.0,694880.0,835.0
691
+ 2022-1-04,15903.0,682878.0,834.0
692
+ 2022-1-05,15772.0,672241.0,828.0
693
+ 2022-1-06,15316.0,663806.0,802.0
694
+ 2022-1-07,16735.0,657719.0,787.0
695
+ 2022-1-08,16568.0,653042.0,796.0
696
+ 2022-1-09,16246.0,647774.0,763.0
697
+ 2022-1-10,15830.0,642973.0,741.0
698
+ 2022-1-11,17525.0,634499.0,783.0
699
+ 2022-1-12,17946.0,625354.0,745.0
700
+ 2022-1-13,21155.0,619785.0,740.0
701
+ 2022-1-14,23820.0,617914.0,739.0
702
+ 2022-1-15,27179.0,617786.0,723.0
703
+ 2022-1-16,29230.0,623599.0,686.0
704
+ 2022-1-17,30726.0,633899.0,670.0
705
+ 2022-1-18,31252.0,639899.0,688.0
706
+ 2022-1-19,33899.0,650180.0,698.0
707
+ 2022-1-20,38850.0,663868.0,684.0
708
+ 2022-1-21,49513.0,687970.0,692.0
709
+ 2022-1-22,57212.0,718976.0,681.0
710
+ 2022-1-23,63205.0,758457.0,679.0
711
+ 2022-1-24,65109.0,801197.0,655.0
712
+ 2022-1-25,67809.0,841921.0,681.0
713
+ 2022-1-26,74692.0,887759.0,657.0
714
+ 2022-1-27,88816.0,946156.0,665.0
715
+ 2022-1-28,98040.0,1014017.0,673.0
716
+ 2022-1-29,113122.0,1096461.0,668.0
717
+ 2022-1-30,121228.0,1188128.0,617.0
718
+ 2022-1-31,124070.0,1281447.0,621.0
719
+ 2022-2-01,125836.0,1366319.0,663.0
720
+ 2022-2-02,141883.0,1459098.0,678.0
721
+ 2022-2-03,155768.0,1560475.0,667.0
722
+ 2022-2-04,168201.0,1669545.0,682.0
723
+ 2022-2-05,177282.0,1785606.0,714.0
724
+ 2022-2-06,180071.0,1905433.0,661.0
725
+ 2022-2-07,171905.0,2021046.0,609.0
726
+ 2022-2-08,165643.0,2104803.0,698.0
727
+ 2022-2-09,183103.0,2190074.0,669.0
728
+ 2022-2-10,197076.0,2280357.0,701.0
729
+ 2022-2-11,203949.0,2371348.0,722.0
730
+ 2022-2-12,203766.0,2461727.0,729.0
731
+ 2022-2-13,197949.0,2557402.0,706.0
732
+ 2022-2-14,180456.0,2639990.0,683.0
733
+ 2022-2-15,166631.0,2668036.0,704.0
734
+ 2022-2-16,179284.0,2674104.0,748.0
735
+ 2022-2-17,180622.0,2668854.0,790.0
736
+ 2022-2-18,180071.0,2649772.0,784.0
737
+ 2022-2-19,179147.0,2637023.0,798.0
738
+ 2022-2-20,170699.0,2659681.0,745.0
739
+ 2022-2-21,152337.0,2683789.0,735.0
740
+ 2022-2-22,135172.0,2653872.0,796.0
741
+ 2022-2-23,137642.0,2611526.0,785.0
742
+ 2022-2-24,132998.0,2607329.0,762.0
743
+ 2022-2-25,123460.0,2562692.0,787.0
744
+ 2022-2-26,122995.0,2503551.0,793.0
745
+ 2022-2-27,116093.0,2481279.0,769.0
746
+ 2022-2-28,106920.0,2470724.0,733.0
747
+ 2022-3-01,97333.0,2394458.0,786.0
748
+ 2022-3-02,97455.0,2307599.0,784.0
749
+ 2022-3-03,93026.0,2229155.0,781.0
750
+ 2022-3-04,89174.0,2157046.0,776.0
751
+ 2022-3-05,86769.0,2087340.0,750.0
752
+ 2022-3-06,79863.0,2017399.0,744.0
753
+ 2022-3-07,73162.0,1967274.0,668.0
754
+ 2022-3-08,66576.0,1921442.0,652.0
755
+ 2022-3-09,58675.0,1886740.0,645.0
756
+ 2022-3-10,51231.0,1788672.0,665.0
757
+ 2022-3-11,50743.0,1687531.0,674.0
758
+ 2022-3-12,48154.0,1578204.0,630.0
759
+ 2022-3-13,44989.0,1493968.0,596.0
760
+ 2022-3-14,41055.0,1429247.0,533.0
761
+ 2022-3-15,36678.0,1346995.0,558.0
762
+ 2022-3-16,36519.0,1264929.0,576.0
763
+ 2022-3-17,34819.0,1186573.0,561.0
764
+ 2022-3-18,34442.0,1115427.0,524.0
765
+ 2022-3-19,32958.0,1052332.0,495.0
766
+ 2022-3-20,31035.0,1008258.0,434.0
767
+ 2022-3-21,28709.0,974620.0,409.0
768
+ 2022-3-22,26394.0,925622.0,472.0
769
+ 2022-3-23,26826.0,881397.0,429.0
770
+ 2022-3-24,25387.0,839890.0,418.0
771
+ 2022-3-25,25382.0,803014.0,398.0
772
+ 2022-3-26,24072.0,765758.0,395.0
773
+ 2022-3-27,23280.0,741160.0,338.0
774
+ 2022-3-28,21101.0,726180.0,335.0
775
+ 2022-3-29,19660.0,698272.0,339.0
776
+ 2022-3-30,20145.0,666980.0,352.0
777
+ 2022-3-31,19277.0,635416.0,345.0
778
+ 2022-4-01,19164.0,608240.0,342.0
779
+ 2022-4-02,17949.0,581302.0,340.0
780
+ 2022-4-03,16828.0,562131.0,304.0
781
+ 2022-4-04,15291.0,544285.0,287.0
782
+ 2022-4-05,13947.0,520457.0,316.0
783
+ 2022-4-06,14661.0,499521.0,291.0
784
+ 2022-4-07,14355.0,478285.0,287.0
785
+ 2022-4-08,14311.0,454632.0,280.0
786
+ 2022-4-09,13573.0,427817.0,288.0
787
+ 2022-4-10,13056.0,411242.0,259.0
788
+ 2022-4-11,11855.0,396094.0,248.0
789
+ 2022-4-12,10910.0,375452.0,281.0
790
+ 2022-4-13,11754.0,358804.0,267.0
791
+ 2022-4-14,11348.0,348045.0,254.0
792
+ 2022-4-15,11432.0,339693.0,261.0
793
+ 2022-4-16,11095.0,331542.0,240.0
794
+ 2022-4-17,10263.0,325076.0,233.0
795
+ 2022-4-18,9434.0,319973.0,213.0
796
+ 2022-4-19,8640.0,311615.0,235.0
797
+ 2022-4-20,9195.0,304387.0,223.0
798
+ 2022-4-21,8875.0,298883.0,197.0
799
+ 2022-4-22,9001.0,294209.0,195.0
800
+ 2022-4-23,8829.0,289837.0,171.0
801
+ 2022-4-24,8446.0,287607.0,168.0
802
+ 2022-4-25,7651.0,286244.0,159.0
803
+ 2022-4-26,7107.0,281276.0,176.0
804
+ 2022-4-27,7705.0,277341.0,163.0
805
+ 2022-4-28,7681.0,273793.0,166.0
806
+ 2022-4-29,7710.0,270301.0,161.0
807
+ 2022-4-30,7363.0,266485.0,157.0
808
+ 2022-5-01,7047.0,264652.0,147.0
809
+ 2022-5-02,6207.0,263221.0,136.0
810
+ 2022-5-03,5466.0,261510.0,125.0
811
+ 2022-5-04,5093.0,259810.0,129.0
812
+ 2022-5-05,5011.0,255959.0,139.0
813
+ 2022-5-06,5541.0,251956.0,136.0
814
+ 2022-5-07,5500.0,248675.0,132.0
815
+ 2022-5-08,5447.0,247322.0,118.0
816
+ 2022-5-09,5030.0,246290.0,103.0
817
+ 2022-5-10,4531.0,245706.0,101.0
818
+ 2022-5-11,4102.0,244667.0,98.0
819
+ 2022-5-12,4065.0,241691.0,111.0
820
+ 2022-5-13,4896.0,239225.0,105.0
821
+ 2022-5-14,5047.0,236787.0,107.0
src/streamlit_app.py CHANGED
The diff for this file is too large to render. See raw diff
 
БЫСТРЫЙ_СТАРТ.md ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ⚡ Быстрый старт
2
+
3
+ ## 🚀 Запуск программы
4
+
5
+ ```bash
6
+ pip install -r requirements.txt
7
+ streamlit run src/streamlit_app.py
8
+ ```
9
+
10
+ Откройте браузер: `http://localhost:8501`
11
+
12
+ ---
13
+
14
+ ## 📋 ЛР №1: Пошаговая инструкция
15
+
16
+ ### Шаг 1: Загрузка данных
17
+ - Загрузите CSV файл или выберите пример
18
+ - Укажите колонку с датами
19
+
20
+ ### Шаг 2: Предобработка
21
+ - Нажмите **"Run Preprocessing"**
22
+ - Оставьте настройки по умолчанию (или измените при необходимости)
23
+
24
+ ### Шаг 3: Статистика
25
+ - Посмотрите таблицу дескриптивной статистики
26
+ - Изучите гистограммы и матрицу корреляций
27
+
28
+ ### Шаг 4: Стационарность
29
+ - Выберите целевую переменную
30
+ - Нажмите **"Run stationarity tests"**
31
+ - Если нестационарен → нажмите **"Apply diff & Re-test"** с `diff_order=1`
32
+
33
+ ### Шаг 5: Лаги
34
+ - Укажите лаги: `1,7,30`
35
+ - Укажите окна: `7,30`
36
+ - Нажмите **"Generate lags & rolls"**
37
+
38
+ ### Шаг 6: ACF/PACF
39
+ - Выберите целевую переменную
40
+ - Установите `max_lag=40`
41
+ - Посмотрите графики и значимые лаги
42
+
43
+ ### Шаг 7: Декомпозиция
44
+ - Выберите модель: `additive`
45
+ - Укажите период: `7` (для недельной) или `30` (для месячной)
46
+ - Нажмите **"Run decomposition"**
47
+
48
+ ### Шаг 8: Отчёт
49
+ - Нажмите **"Сгенерировать и показать отчёт"**
50
+ - Скачайте HTML-отчёт
51
+
52
+ ---
53
+
54
+ ## 🔮 ЛР №2: Пошаговая инструкция
55
+
56
+ ### Шаг 1: Подготовка
57
+ - Убедитесь, что данные из ЛР №1 загружены
58
+ - Выберите целевую переменную
59
+ - Установите горизонт прогнозирования (h): `7` или `30`
60
+
61
+ ### Шаг 2: Декомпозиция
62
+ - Выберите период сезонности: `7` или `30`
63
+ - Нажмите **"Выполнить декомпозицию"**
64
+
65
+ ### Шаг 3: Преобразования (опционально)
66
+ - Начните с `none` и `diff_order=0`
67
+ - Если модель плохая → попробуйте `diff_order=1`
68
+
69
+ ### Шаг 4: Модели
70
+ - Выберите стратегии: `recursive` (для начала)
71
+ - Выберите модели: `SES`, `Holt_add`
72
+ - Нажмите **"Применить преобразования и построить модели"**
73
+
74
+ ### Шаг 5: Результаты
75
+ - Посмотрите таблицу метрик
76
+ - Найдите модель с наименьшими MAE, RMSE, MAPE
77
+ - Посмотрите график прогнозов
78
+
79
+ ### Шаг 6: Диагностика
80
+ - Выберите лучшую модель
81
+ - Нажмите **"Выполнить диагностику остатков"**
82
+ - Проверьте, что p-value тестов > 0.05
83
+
84
+ ### Шаг 7: Выводы
85
+ - Посмотрите итоговую таблицу
86
+ - Выберите лучшую модель
87
+ - Скачайте прогнозы и параметры модели
88
+
89
+ ---
90
+
91
+ ## 📊 Что означают метрики
92
+
93
+ | Метрика | Что означает | Чем меньше, тем лучше |
94
+ |---------|-------------|------------------------|
95
+ | **MAE** | Средняя абсолютная ошибка | ✅ |
96
+ | **RMSE** | Корень из средней квадратичной ошибки | ✅ |
97
+ | **MAPE** | Средняя абсолютная процентная ошибка (%) | ✅ |
98
+
99
+ **Пример:** MAPE = 10% означает, что в среднем ошибка 10%
100
+
101
+ ---
102
+
103
+ ## 🎯 Тесты стационарности
104
+
105
+ | Тест | Стационарен если | Нестационарен если |
106
+ |------|------------------|---------------------|
107
+ | **ADF** | p-value < 0.05 ✅ | p-value >= 0.05 ❌ |
108
+ | **KPSS** | p-value > 0.05 ✅ | p-value <= 0.05 ❌ |
109
+
110
+ **Если нестационарен:** примените `diff_order=1`
111
+
112
+ ---
113
+
114
+ ## 🔍 Корреляции
115
+
116
+ | Значение | Интерпретация |
117
+ |----------|---------------|
118
+ | **1.0** | Полная прямая связь |
119
+ | **0.7-1.0** | Сильная связь |
120
+ | **0.3-0.7** | Умеренная связь |
121
+ | **0.0-0.3** | Слабая связь |
122
+ | **0.0** | Нет связи |
123
+ | **-1.0** | Полная обратная связь |
124
+
125
+ **Проблема:** Если два признака коррелируют > 0.8 → мультиколлинеарность
126
+
127
+ ---
128
+
129
+ ## 🎨 Модели экспоненциального сглаживания
130
+
131
+ | Модель | Когда использовать |
132
+ |--------|-------------------|
133
+ | **SES** | Стационарный ряд без тренда |
134
+ | **Holt Additive** | Ряд с линейным трендом |
135
+ | **Holt Multiplicative** | Ряд с экспоненциальным трендом |
136
+
137
+ ---
138
+
139
+ ## 🚦 Стратегии прогнозирования
140
+
141
+ | Стратегия | Когда использовать |
142
+ |-----------|-------------------|
143
+ | **Recursive** | Короткий горизонт (h < 7) |
144
+ | **Direct** | Длинный горизонт (h >= 30) |
145
+ | **Hybrid** | Средний горизонт (7-30) |
146
+
147
+ ---
148
+
149
+ ## ⚠️ Частые ошибки
150
+
151
+ 1. **"Недостаточно данных"**
152
+ - Решение: уменьшите размер обучающей выборки
153
+
154
+ 2. **"Для лог-трансформации все значения должны быть положительными"**
155
+ - **Причина:** В данных есть нули или отрицательные значения
156
+ - **Решения:**
157
+ - ✅ **Используйте Box-Cox** вместо логарифма (автоматически обработает проблему)
158
+ - ✅ **Используйте только дифференцирование** (diff_order=1) без логарифма
159
+ - ✅ **Включите автоматический сдвиг** - программа предложит сдвинуть данные
160
+ - **Как исправить:**
161
+ 1. Посмотрите статистику данных в сообщении об ошибке
162
+ 2. Включите чекбокс "Автоматически сдвинуть данные"
163
+ 3. Или измените тип преобразования на "boxcox"
164
+
165
+ 3. **"Ошибка при преобразовании"**
166
+ - Решение: убедитесь, что все значения положительные (для log/boxcox)
167
+
168
+ 4. **"Модель работает плохо"**
169
+ - Решение: попробуйте `diff_order=1` или другую модель
170
+
171
+ 5. **"Остатки имеют автокорреляцию"**
172
+ - Решение: попробуйте другую модель или стратегию
173
+
174
+ ---
175
+
176
+ ## 💡 Советы
177
+
178
+ 1. ✅ Начинайте с простого (SES, recursive, без преобразований)
179
+ 2. ✅ Сравнивайте с наивным прогнозом (baseline)
180
+ 3. ✅ Проверяйте диагностику остатков
181
+ 4. ✅ Экспериментируйте с разными моделями
182
+ 5. ✅ Сохраняйте результаты (CSV, HTML-отчёты)
183
+
184
+ ---
185
+
186
+ ## 📁 Что скачивать
187
+
188
+ - **final_dataset.csv** - очищенные данные (ЛР №1)
189
+ - **dataset_with_lags.csv** - данные с лагами (ЛР №1)
190
+ - **ts_report.html** - HTML-отчёт (ЛР №1)
191
+ - **forecast_*.csv** - прогнозы моделей (ЛР №2)
192
+ - **model_params_*.csv** - параметры моделей (ЛР №2)
193
+
194
+ ---
195
+
196
+ **Подробное руководство:** см. `РУКОВОДСТВО.md`
197
+
РУКОВОДСТВО.md ADDED
@@ -0,0 +1,655 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 📚 Руководство по использованию программы для анализа временных рядов
2
+
3
+ ## 🎯 Общая структура проекта
4
+
5
+ Ваш проект состоит из двух лабораторных работ, объединённых в одно веб-приложение:
6
+
7
+ ### Файлы проекта:
8
+ - **`src/streamlit_app.py`** - главный файл веб-приложения (1655 строк)
9
+ - **`src/lab2_functions.py`** - функции для лабораторной работы №2 (604 строки)
10
+ - **`src/main.py`** - не используется (можно игнорировать)
11
+ - **`russia_covid_dataset.csv`** - пример данных (COVID-19 по России)
12
+
13
+ ---
14
+
15
+ ## 🚀 Как запустить программу
16
+
17
+ 1. **Установите зависимости:**
18
+ ```bash
19
+ pip install -r requirements.txt
20
+ ```
21
+
22
+ 2. **Запустите приложение:**
23
+ ```bash
24
+ streamlit run src/streamlit_app.py
25
+ ```
26
+
27
+ 3. **Откройте браузер:**
28
+ - Программа автоматически откроется на `http://localhost:8501`
29
+ - Или откройте вручную этот адрес
30
+
31
+ ---
32
+
33
+ ## 📖 ЛАБОРАТОРНАЯ РАБОТА №1: Введение в анализ временных рядов
34
+
35
+ ### Что делает эта работа?
36
+
37
+ Эта работа учит вас **"читать"** временной ряд - понимать его структуру, находить закономерности, выявлять проблемы.
38
+
39
+ ### Порядок работы (по этапам):
40
+
41
+ #### **Этап 1: Загрузка данных**
42
+ 1. В боковой панели нажмите "Загрузите CSV/Parquet"
43
+ 2. Или выберите предзагруженный пример (если есть `russia_covid_dataset.csv`)
44
+ 3. Укажите колонку с датами (программа попытается найти её автоматически)
45
+
46
+ **Что происходит:** Программа загружает ваш файл и показывает первые строки.
47
+
48
+ ---
49
+
50
+ #### **Этап 2: Предобработка данных (Preprocessing)**
51
+
52
+ **Настройки в боковой панели:**
53
+ - **Как трактовать tz-naive метки?** - как обрабатывать даты без временной зоны
54
+ - **Заполнение пропусков (числ.)** - что делать с пропущенными числами:
55
+ - `interpolate` - заполнить интерполяцией (рекомендуется)
56
+ - `drop` - удалить строки
57
+ - `rolling` - заполнить скользящим средним
58
+ - **Обработка выбросов** - что делать с аномальными значениями:
59
+ - `interpolate` - заменить интерполяцией
60
+ - `winsorize` - обрезать экстремальные значения
61
+ - `drop` - удалить
62
+ - `mark` - только отметить
63
+ - **Ресемплить к частоте** - изменить частоту данных (D=день, W=неделя, M=месяц)
64
+
65
+ **Что делать:**
66
+ 1. Нажмите кнопку **"Run Preprocessing"**
67
+ 2. Программа покажет:
68
+ - Сколько строк было до/после обработки
69
+ - Сколько пропусков найдено и обработано
70
+ - Сколько выбросов обнаружено
71
+
72
+ **Результат:** Очищенный датасет, готовый к анализу.
73
+
74
+ ---
75
+
76
+ #### **Этап 3: Описательная статистика и визуализация**
77
+
78
+ **Что показывается:**
79
+
80
+ 1. **Таблица дескриптивной статистики:**
81
+ - `count` - количество наблюдений
82
+ - `mean` - среднее значение
83
+ - `median` - медиана (середина)
84
+ - `std` - стандартное отклонение (разброс)
85
+ - `min/max` - минимум/максимум
86
+ - `q1/q3` - первый и третий квартили (25% и 75%)
87
+ - `skew` - асимметрия (если >0, хвост справа)
88
+ - `kurtosis` - эксцесс (острота распределения)
89
+
90
+ 2. **Гистограммы и Boxplot:**
91
+ - **Гистограмма** - показывает распределение значений (как часто встречается каждое значение)
92
+ - **Boxplot** - показывает медиану, квартили и выбросы
93
+
94
+ 3. **Матрица корреляций:**
95
+ - Показывает, насколько связаны между собой признаки
96
+ - Значения от -1 до 1:
97
+ - **1** = полная прямая связь
98
+ - **0** = нет связи
99
+ - **-1** = полная ��братная связь
100
+ - **Важно:** Если два признака сильно коррелируют (>0.8), это может быть проблемой (мультиколлинеарность)
101
+
102
+ **Как интерпретировать:**
103
+ - Если `std` большой относительно `mean` - данные сильно разбросаны
104
+ - Если `skew` далёк от 0 - распределение несимметрично
105
+ - Сильные корреляции (>0.7) указывают на зависимость признаков
106
+
107
+ ---
108
+
109
+ #### **Этап 4: Проверка на стационарность**
110
+
111
+ **Что такое стационарность?**
112
+ Стационарный ряд = ряд без тренда, с постоянной дисперсией. Многие модели требуют стационарности.
113
+
114
+ **Что показывается:**
115
+
116
+ 1. **График ряда с rolling mean:**
117
+ - Если линия скользящего среднего не горизонтальна → есть тренд (нестационарен)
118
+ - Если линия скользящего среднего горизонтальна → тренда нет (стационарен)
119
+
120
+ 2. **График rolling std:**
121
+ - Если линия не горизонтальна → дисперсия меняется (нестационарен)
122
+ - Если горизонтальна → дисперсия постоянна
123
+
124
+ 3. **Статистические тесты:**
125
+ - **ADF (Augmented Dickey-Fuller):**
126
+ - p-value < 0.05 → ряд стационарен ✅
127
+ - p-value >= 0.05 → ряд нестационарен ❌
128
+ - **KPSS:**
129
+ - p-value > 0.05 → ряд стационарен ✅
130
+ - p-value <= 0.05 → ряд нестационарен ❌
131
+
132
+ **Что делать, если ряд нестационарен:**
133
+ 1. Нажмите "Apply diff & Re-test" с `diff_order=1`
134
+ 2. Это применит дифференцирование (убирает тренд)
135
+ 3. Повторите тесты
136
+
137
+ ---
138
+
139
+ #### **Этап 5: Создание лагов и скользящих статистик**
140
+
141
+ **Что такое лаги?**
142
+ Лаг = значение переменной в прошлом. Например, `target_lag_7` = значение target 7 дней назад.
143
+
144
+ **Что создаётся:**
145
+ - **Лаги:** `target_lag_1`, `target_lag_7`, `target_lag_30`
146
+ - **Скользящие статистики:**
147
+ - `target_rolling_mean_7` - среднее за последние 7 дней
148
+ - `target_rolling_std_7` - стандартное отклонение за последние 7 дней
149
+
150
+ **Как использовать:**
151
+ 1. Укажите целевую переменную (target)
152
+ 2. Укажите лаги через запятую (например: `1,7,30`)
153
+ 3. Укажите окна для скользящих (например: `7,30`)
154
+ 4. Нажмите "Generate lags & rolls"
155
+
156
+ **Что показывается:**
157
+ - Таблица корреляций лагов с target - какие лаги наиболее информативны
158
+ - Heatmap корреляций - визуализация всех корреляций
159
+ - VIF (Variance Inflation Factor) - проверка мультиколлинеарности:
160
+ - VIF < 5 → нормально
161
+ - VIF 5-10 → умеренная мультиколлинеарность
162
+ - VIF > 10 → сильная мультиколлинеарность (проблема)
163
+
164
+ ---
165
+
166
+ #### **Этап 6: ACF и PACF**
167
+
168
+ **Что это такое?**
169
+ - **ACF (Autocorrelation Function)** - корреляция ряда с его лагами
170
+ - **PACF (Partial Autocorrelation Function)** - "чистая" корреляция с лагом
171
+
172
+ **Как интерпретировать графики:**
173
+
174
+ 1. **ACF:**
175
+ - Плавное затухание → возможный порядок MA(q)
176
+ - Резкий обрыв → возможный порядок AR(p)
177
+
178
+ 2. **PACF:**
179
+ - Резкий обрыв на лаге p → возможный порядок AR(p)
180
+ - Плавное затухание → возможный порядок MA(q)
181
+
182
+ 3. **Значимые лаги:**
183
+ - Лаги, выходящие за доверительный интервал (синие линии) → статистически значимы
184
+ - Эти лаги важны для моделирования
185
+
186
+ **Что делать:**
187
+ - Посмотрите, какие лаги значимы
188
+ - Запомните порядок обрыва в PACF - это может быть порядок AR модели
189
+
190
+ ---
191
+
192
+ #### **Этап 7: Декомпозиция временного ряда**
193
+
194
+ **Что такое декомпозиция?**
195
+ Разложение ряда на компоненты:
196
+ - **Observed** - исходный ряд
197
+ - **Trend** - тренд (долгосрочная тенденция)
198
+ - **Seasonal** - сезонность (периодические колебания)
199
+ - **Residual** - остатки (случайные колебания)
200
+
201
+ **Модели декомпозиции:**
202
+ - **Additive (аддитивная):** `value = trend + seasonal + residual`
203
+ - Используйте, если амплитуда сезонности постоянна
204
+ - **Multiplicative (мультипликативная):** `value = trend × seasonal × residual`
205
+ - Используйте, если амплитуда сезонности растёт со временем
206
+
207
+ **Как использовать:**
208
+ 1. Выберите целевую переменную
209
+ 2. Выберите модель (additive/multiplicative)
210
+ 3. Укажите период сезонности:
211
+ - 7 - для недельной сезонности
212
+ - 30 - для месячной
213
+ - 365 - для годовой
214
+ 4. Нажмите "Run decomposition"
215
+
216
+ **Что показывается:**
217
+ - Графики всех компонентов
218
+ - Анализ тренда (растёт/падает)
219
+ - Амплитуда сезонности
220
+ - Диагностика остатков (должны быть стационарны и случайны)
221
+
222
+ **Как интерпретировать:**
223
+ - Если остатки стационарны (ADF/KPSS тесты) → декомпозиция хорошая ✅
224
+ - Если остатки нестационарны → попробуйте другой период или модель ❌
225
+
226
+ ---
227
+
228
+ #### **Этап 8: Генерация отчёта**
229
+
230
+ **Что делает:**
231
+ Собирает все графики и таблицы в один HTML-отчёт.
232
+
233
+ **Как использовать:**
234
+ 1. Настройте параметры в разделе "Параметры для отчёта"
235
+ 2. Нажмите "Сгенерировать и показать отчёт"
236
+ 3. Просмотрите результаты во вкладках
237
+ 4. Скачайте HTML-отчёт
238
+
239
+ ---
240
+
241
+ ## 🔮 ЛАБОРАТОРНАЯ РАБОТА №2: Прогнозирование временных рядов
242
+
243
+ ### Что делает эта работа?
244
+
245
+ Эта работа учит строить **модели для прогнозирования** будущих значений временного ряда.
246
+
247
+ ### Порядок работы:
248
+
249
+ #### **Этап 1: Декомпозиция и анализ остатков**
250
+
251
+ **Что делать:**
252
+ 1. Убедитесь, что данные из ЛР №1 загружены
253
+ 2. Выберите целевую переменную в боковой панели
254
+ 3. Укажите горизонт прогнозирования (h) - на сколько шагов вперёд прогнозировать
255
+ 4. Настройте размер обучающей выборки (рекомендуется ≥500)
256
+ 5. Нажмите "Выполнить декомпозицию"
257
+
258
+ **Что показывается:**
259
+ - Графики компонентов (observed, trend, seasonal, residuals)
260
+ - Анализ остатков (должны быть стационарны)
261
+ - ACF/PACF остатков (не должно быть значимых лагов)
262
+
263
+ **Как интерпретировать:**
264
+ - Если остатки стационарны и не имеют автокорреляции → декомпозиция хорошая ✅
265
+ - Если нет → попробуйте другой период или модель
266
+
267
+ ---
268
+
269
+ #### **Этап 2: Feature Engineering**
270
+
271
+ **Что создаётся:**
272
+ - Временные признаки (день недели, месяц, квартал)
273
+ - Циклические признаки (sin/cos для периодичности)
274
+ - Лаги (lag_1, lag_7, lag_30)
275
+ - Скользящие статистики (mean, std, min, max)
276
+
277
+ **Как использовать:**
278
+ 1. Нажмите "Создать расширенные признаки"
279
+ 2. Просмотрите созданные признаки
280
+ 3. Скачайте датасет с признаками (опционально)
281
+
282
+ ---
283
+
284
+ #### **Этап 3: Стратегии прогнозирования**
285
+
286
+ **Три стратегии:**
287
+
288
+ 1. **Recursive (рекурсивная):**
289
+ - Одна модель
290
+ - Использует свои предыдущие прогнозы
291
+ - ✅ Хорошо для короткого горизонта (h < 7)
292
+ - ❌ Ошибка накапливается на длинном горизонте
293
+
294
+ 2. **Direct (прямая):**
295
+ - Отдельная модель для каждого шага
296
+ - Прогнозы независимы
297
+ - ✅ Хорошо для длинного горизонта (h >= 30)
298
+ - ❌ Требует больше вычислений
299
+
300
+ 3. **Hybrid (гибридная):**
301
+ - Комбинация: рекурсивная для ближних шагов, прямая для дальних
302
+ - ✅ Баланс ме��ду точностью и скоростью
303
+
304
+ **Как использовать:**
305
+ - Выберите стратегии в разделе "Этап 6-7"
306
+ - Можно выбрать несколько для сравнения
307
+
308
+ ---
309
+
310
+ #### **Этап 4: Кросс-валидация**
311
+
312
+ **Что такое кросс-валидация?**
313
+ Проверка качества модели на разных частях данных без утечки будущего.
314
+
315
+ **Методы:**
316
+ 1. **Sliding window (скользящее окно):**
317
+ - Фиксированная длина обучения
318
+ - Окно "скользит" по времени
319
+
320
+ 2. **Expanding window (расширяющееся окно):**
321
+ - Длина обучения растёт
322
+ - Более реалистично для реальных задач
323
+
324
+ 3. **TimeSeriesSplit:**
325
+ - Стандартный метод из sklearn
326
+ - Прогрессивное разбиение
327
+
328
+ **Как использовать:**
329
+ 1. Выберите метод
330
+ 2. Настройте размеры train/test
331
+ 3. Нажмите "Выполнить кросс-валидацию"
332
+
333
+ **Что показывается:**
334
+ - Таблица метрик по фолдам
335
+ - Средние метрики
336
+ - График метрик по фолдам
337
+
338
+ **Как интерпретировать:**
339
+ - Если метрики стабильны по фолдам → модель надёжна ✅
340
+ - Если метрики сильно меняются → модель нестабильна ❌
341
+
342
+ ---
343
+
344
+ #### **Этап 5: Преобразования к стационарности**
345
+
346
+ **Зачем нужно?**
347
+ Многие модели требуют стационарных данных.
348
+
349
+ **Типы преобразований:**
350
+
351
+ 1. **None (без преобразования):**
352
+ - Используйте, если данные уже стационарны
353
+
354
+ 2. **Log (логарифм):**
355
+ - Стабилизирует дисперсию
356
+ - Требует положительные значения
357
+ - Используйте, если дисперсия растёт со временем
358
+
359
+ 3. **Box-Cox:**
360
+ - Автоматический подбор преобразования
361
+ - Включает логарифм как частный случай
362
+ - ✅ Рекомендуется, если не уверены
363
+
364
+ 4. **Дифференцирование:**
365
+ - `diff_order=1` - убирает тренд (первая разность)
366
+ - `diff_order=2` - убирает квадратичный тренд
367
+ - `seasonal_diff=7` - убирает сезонность (для недельной)
368
+
369
+ **Как использовать:**
370
+ 1. Начните с `none` и `diff_order=0`
371
+ 2. Если модель плохая, попробуйте `diff_order=1`
372
+ 3. Если дисперсия нестабильна, попробуйте `log` или `boxcox`
373
+
374
+ **Что показывается:**
375
+ - Результаты тестов ADF/KPSS после преобразования
376
+ - Если p-value ADF < 0.05 и p-value KPSS > 0.05 → стационарен ✅
377
+
378
+ ---
379
+
380
+ #### **Этап 6-7: Модели экспоненциального сглаживания**
381
+
382
+ **Три модели:**
383
+
384
+ 1. **SES (Simple Exponential Smoothing):**
385
+ - Простое сглаживание без тренда
386
+ - ✅ Хорошо для стационарных рядов
387
+ - ❌ Не учитывает тренд
388
+
389
+ 2. **Holt Additive:**
390
+ - С аддитивным трендом (линейный рост/падение)
391
+ - ✅ Хорошо для рядов с линейным трендом
392
+ - Формула: `level + trend`
393
+
394
+ 3. **Holt Multiplicative:**
395
+ - С мультипликативным трендом (экспоненциальный рост/падение)
396
+ - ✅ Хорошо для рядов с экспоненциальным трендом
397
+ - ❌ Требует положительные значения
398
+ - Формула: `level × trend`
399
+
400
+ **Как использовать:**
401
+ 1. Выберите стратегии прогнозирования
402
+ 2. Выберите модели (можно несколько)
403
+ 3. Нажмите "Применить преобразования и построить модели"
404
+
405
+ **Что показывается:**
406
+ - Таблица сравнения метрик (MAE, RMSE, MAPE)
407
+ - График прогнозов всех моделей
408
+ - Сравнение с наивным прогнозом (baseline)
409
+
410
+ **Метрики качества:**
411
+
412
+ 1. **MAE (Mean Absolute Error):**
413
+ - Средняя абсолютная ошибка
414
+ - Чем меньше, тем лучше
415
+ - Интерпретация: среднее отклонение прогноза от реальности
416
+
417
+ 2. **RMSE (Root Mean Squared Error):**
418
+ - Корень из средней квадратичной ошибки
419
+ - Чем меньше, те�� лучше
420
+ - Более чувствительна к большим ошибкам
421
+ - Интерпретация: типичное отклонение (с учётом больших ошибок)
422
+
423
+ 3. **MAPE (Mean Absolute Percentage Error):**
424
+ - Средняя абсолютная процентная ошибка
425
+ - Чем меньше, тем лучше
426
+ - Интерпретация: ошибка в процентах
427
+ - Пример: MAPE=5% означает, что в среднем ошибка 5%
428
+
429
+ **Как интерпретировать:**
430
+ - Сравните метрики моделей
431
+ - Выберите модель с наименьшими MAE, RMSE, MAPE
432
+ - Убедитесь, что модель лучше наивного прогноза
433
+
434
+ ---
435
+
436
+ #### **Этап 7: Диагностика остатков**
437
+
438
+ **Что проверяется:**
439
+
440
+ 1. **Тест Льюнга-Бокса:**
441
+ - Проверяет автокорреляцию в остатках
442
+ - p-value < 0.05 → есть автокорреляция (плохо) ❌
443
+ - p-value >= 0.05 → нет автокорреляции (хорошо) ✅
444
+
445
+ 2. **Тест Шапиро-Уилка:**
446
+ - Проверяет нормальность распределения остатков
447
+ - p-value < 0.05 → не нормально (плохо) ❌
448
+ - p-value >= 0.05 → нормально (хорошо) ✅
449
+
450
+ 3. **Графики:**
451
+ - **Остатки vs Прогнозы:** должны быть случайными (гомоскедастичность)
452
+ - **Q-Q Plot:** точки должны лежать на прямой (нормальность)
453
+ - **Временной ряд остатков:** не должно быть тренда
454
+
455
+ **Как использовать:**
456
+ 1. Выберите модель для диагностики
457
+ 2. Нажмите "Выполнить диагностику остатков"
458
+
459
+ **Как интерпретировать:**
460
+ - Если все тесты пройдены → модель адекватна ✅
461
+ - Если есть проблемы → попробуйте другую модель или добавьте преобразования
462
+
463
+ ---
464
+
465
+ #### **Этап 8: Сравнительный анализ и выводы**
466
+
467
+ **Что показывается:**
468
+ - Итоговая таблица метрик всех моделей
469
+ - Лучшие модели по каждой метрике
470
+ - Рекомендации по выбору модели
471
+
472
+ **Как использовать:**
473
+ 1. Посмотрите на таблицу метрик
474
+ 2. Найдите модель с наименьшими ошибками
475
+ 3. Проверьте диагностику остатков для этой модели
476
+ 4. Используйте эту модель для прогнозирования
477
+
478
+ **Рекомендации:**
479
+ - **Короткий горизонт (h < 7):** рекурсивная стратегия
480
+ - **Длинный горизонт (h >= 30):** прямая или гибридная стратегия
481
+ - **Нестационарный ряд:** используйте дифференцирование (diff_order=1)
482
+ - **Нестабильная дисперсия:** используйте Box-Cox преобразование
483
+
484
+ ---
485
+
486
+ ## 📊 Что означают числа и метрики
487
+
488
+ ### Статистические метрики:
489
+
490
+ - **Mean (среднее):** среднее значение всех наблюдений
491
+ - **Median (медиана):** значение в середине (50% наблюдений меньше)
492
+ - **Std (стандартное отклонение):** мера разброса данных
493
+ - Маленькое std → данные сконцентрированы
494
+ - Большое std → данные разбросаны
495
+ - **Min/Max:** минимальное и максимальное значения
496
+ - **Q1/Q3 (квартили):** 25% и 75% наблюдений меньше этого значения
497
+ - **Skew (асимметрия):**
498
+ - 0 = симметричное распределение
499
+ - >0 = хвост справа (больше больших значений)
500
+ - <0 = хвост слева (больше маленьких значений)
501
+ - **Kurtosis (эксцесс):**
502
+ - 0 = нормальное распределение
503
+ - >0 = более острое распределение (больше экстремальных значений)
504
+ - <0 = более плоское распределение
505
+
506
+ ### Метрики качества прогноза:
507
+
508
+ - **MAE:** средняя абсолютная ошибка (в тех же единицах, что и данные)
509
+ - **RMSE:** корень из средней квадратичной ошибки (более чувствительна к большим ошибкам)
510
+ - **MAPE:** средняя абсолютная процентная ошибка (в процентах)
511
+
512
+ **Пример интерпретации:**
513
+ - Если MAPE = 10%, это означает, что в среднем прогноз отклоняется на 10% от реальности
514
+ - Если MAE = 100, это означает, что в среднем прогноз отклоняется на 100 единиц
515
+
516
+ ### Тесты стационарности:
517
+
518
+ - **ADF p-value:**
519
+ - < 0.05 → ряд стационарен ✅
520
+ - >= 0.05 → ряд нестационарен ❌
521
+ - **KPSS p-value:**
522
+ - > 0.05 → ряд стационарен ✅
523
+ - <= 0.05 → ряд нестационарен ❌
524
+
525
+ ### Корреляции:
526
+
527
+ - **1.0:** полная прямая связь (когда один растёт, другой тоже растёт)
528
+ - **0.0:** нет связи
529
+ - **-1.0:** полная обратная связь (когда один растёт, другой падает)
530
+ - **0.7-1.0:** сильная связь
531
+ - **0.3-0.7:** умеренная связь
532
+ - **0.0-0.3:** слабая связь
533
+
534
+ ---
535
+
536
+ ## 🎓 Типичный workflow (порядок работы)
537
+
538
+ ### Для ЛР №1:
539
+
540
+ 1. Загрузите данные
541
+ 2. Запустите предобработку
542
+ 3. Посмотрите описательную статистику
543
+ 4. Проверьте стационарность
544
+ 5. Создайте лаги и скользящие статистики
545
+ 6. Постройте ACF/PACF
546
+ 7. Выполните декомпозицию
547
+ 8. Сгенерируйте отчёт
548
+
549
+ ### Для ЛР №2:
550
+
551
+ 1. Убедитесь, что данные из ЛР №1 загружены
552
+ 2. Выполните декомпозицию
553
+ 3. (Опционально) Создайте расширенные признаки
554
+ 4. (Опционально) Выполните кросс-валидацию
555
+ 5. Настройте преобразования (начните с none, diff_order=0)
556
+ 6. Постройте модели (выберите несколько для сравнения)
557
+ 7. Проверьте диагностику остатков для лучшей модели
558
+ 8. Посмотрите итоговые результаты и выберите лучшую модель
559
+
560
+ ---
561
+
562
+ ## ⚠️ Частые проблемы и решения
563
+
564
+ ### Проблема: "Недостаточно данных"
565
+ **Решение:** Уменьшите размер обучающей выборки или увеличьте тестовую
566
+
567
+ ### Проблема: "Для лог-трансформации все значения должны быть положительными"
568
+ **Причина:**
569
+ Логарифм можно применять только к положительным числам. Если в данных есть нули или отрицательные значения, логарифм не может быть вычислен.
570
+
571
+ **Что покажет программа:**
572
+ - Количество неположительных значений
573
+ - Минимальное и максимальное значения
574
+ - График распределения данных
575
+ - Предложение автоматического сдвига
576
+
577
+ **Решения:**
578
+
579
+ 1. **Используйте Box-Cox преобразование** (рекомендуется):
580
+ - Измените тип преобразования с `log` на `boxcox`
581
+ - Box-Cox автоматически обработает проблему (требует только положительные значения, но может работать с нулями через сдвиг)
582
+
583
+ 2. **Используйте только дифференцирование**:
584
+ - Оставьте `transformation='none'`
585
+ - Установите `diff_order=1`
586
+ - Это уберёт тренд без необходимости логарифма
587
+
588
+ 3. **Автоматический сдвиг данных**:
589
+ - Включите чекбокс "Автоматически сдвинуть данные"
590
+ - Программа добавит константу ко всем значениям, чтобы сделать их положительными
591
+ - Минимум станет равным 1 (или больше)
592
+
593
+ 4. **Вручную сдвиньте данные** (перед загрузкой):
594
+ - Если данные содержат отрицательные значения или нули
595
+ - Добавьте константу ко всем значениям в CSV файле
596
+ - Например: `new_value = old_value + abs(min_value) + 1`
597
+
598
+ **Как выбрать решение:**
599
+ - Если данные близки к нулю → используйте Box-Cox
600
+ - Если нужно сохранить интерпретацию → используйте только дифференцирование
601
+ - Если данные уже в логарифмической шкале → проверьте, не применяли ли в�� логарифм дважды
602
+
603
+ ### Проблема: "Ошибка при преобразовании" (общая)
604
+ **Решение:**
605
+ - Для логарифма: убедитесь, что все значения положительные (см. выше)
606
+ - Для Box-Cox: убедитесь, что все значения положительные (или используйте автоматический сдвиг)
607
+
608
+ ### Проблема: "Модель работает плохо"
609
+ **Решение:**
610
+ 1. Попробуйте дифференцирование (diff_order=1)
611
+ 2. Попробуйте Box-Cox преобразование
612
+ 3. Попробуйте другую модель (например, Holt вместо SES)
613
+ 4. Проверьте, стационарен ли ряд
614
+
615
+ ### Проблема: "Остатки имеют автокорреляцию"
616
+ **Решение:**
617
+ 1. Попробуйте другую модель
618
+ 2. Добавьте больше лагов
619
+ 3. Попробуйте другую стратегию прогнозирования
620
+
621
+ ---
622
+
623
+ ## 💡 Советы
624
+
625
+ 1. **Начинайте просто:** сначала попробуйте модель без преобразований
626
+ 2. **Сравнивайте с baseline:** убедитесь, что ваша модель лучше наивного прогноза
627
+ 3. **Проверяйте диагностику:** хорошая модель должна иметь "хорошие" остатки
628
+ 4. **Экспериментируйте:** пробуйте разные комбинации моделей и стратегий
629
+ 5. **Документируйте:** записывайте, что пробовали и какие результаты получили
630
+
631
+ ---
632
+
633
+ ## 📝 Что делать с результатами
634
+
635
+ 1. **Сохраните очищенные данные:** скачайте `final_dataset.csv`
636
+ 2. **Сохраните прогнозы:** скачайте прогнозы лучшей модели
637
+ 3. **Сохраните параметры модели:** скачайте параметры для воспроизведения
638
+ 4. **Создайте отчёт:** используйте HTML-отчёт из ЛР №1
639
+ 5. **Задокументируйте выводы:** запишите, какая модель лучше и почему
640
+
641
+ ---
642
+
643
+ ## 🎯 Ключевые выводы
644
+
645
+ - **ЛР №1** учит понимать структуру данных
646
+ - **ЛР №2** учит строить модели для прогнозирования
647
+ - **Метрики** показывают качество модели
648
+ - **Диагностика** проверяет адекватность модели
649
+ - **Преобразования** нужны для стационарности
650
+ - **Стратегии** влияют на качество прогноза
651
+
652
+ ---
653
+
654
+ **Удачи в работе! 🚀**
655
+
СТРУКТУРА_КОДА.md ADDED
@@ -0,0 +1,334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🏗️ Структура кода проекта
2
+
3
+ ## 📁 Файловая структура
4
+
5
+ ```
6
+ TimeSeriesHomework/
7
+ ├── src/
8
+ │ ├── streamlit_app.py # Главное веб-приложение (1655 строк)
9
+ │ ├── lab2_functions.py # Функции для ЛР №2 (604 строки)
10
+ │ ├── main.py # Не используется (можно игнорировать)
11
+ │ └── russia_covid_dataset.csv # Пример данных
12
+ ├── requirements.txt # Зависимости Python
13
+ ├── README.md # Описание проекта
14
+ ├── РУКОВОДСТВО.md # Подробное руководство (этот файл)
15
+ ├── БЫСТРЫЙ_СТАРТ.md # Краткая шпаргалка
16
+ └── СТРУКТУРА_КОДА.md # Этот файл
17
+ ```
18
+
19
+ ---
20
+
21
+ ## 🔍 streamlit_app.py - Главное приложение
22
+
23
+ ### Структура файла:
24
+
25
+ ```
26
+ 1. Импорты (строки 1-34)
27
+ ├── Библиотеки: pandas, numpy, streamlit, plotly
28
+ ├── Статистика: statsmodels, scipy
29
+ └── Импорт функций из lab2_functions.py
30
+
31
+ 2. Утилиты (строки 45-94)
32
+ ├── detect_date_column() - поиск колонки с датами
33
+ ├── try_parse_dates() - парсинг дат
34
+ ├── localize_to_moscow() - приведение к часовому поясу
35
+ ├── detect_outliers_iqr() - поиск выбросов
36
+ └── winsorize_series() - обработка выбросов
37
+
38
+ 3. Предобработка (строки 96-195)
39
+ └── preprocess_timeseries() - основная функция очистки данных
40
+ ├── Парсинг дат
41
+ ├── Обработка пропусков
42
+ ├── Обработка выбросов
43
+ └── Ресемплирование
44
+
45
+ 4. Описательная статистика (строки 198-217)
46
+ └── descriptive_statistics() - расчёт статистик
47
+
48
+ 5. Стационарность (строки 220-234)
49
+ ├── run_adf() - тест Дики-Фуллера
50
+ └── run_kpss() - тест KPSS
51
+
52
+ 6. Лаги и скользящие (строки 237-274)
53
+ ├── create_lags_and_rolls() - создание лагов
54
+ ├── compute_lag_correlations() - корреляции лагов
55
+ └── compute_vif() - проверка мультиколлинеарности
56
+
57
+ 7. ACF/PACF (строки 277-316)
58
+ ├── get_acf_pacf_with_conf() - расчёт ACF/PACF
59
+ ├── significant_lags_from_conf() - значимые лаги
60
+ └── plotly_acf_pacf() - визуализация
61
+
62
+ 8. Генерация отчёта (строки 319-359)
63
+ └── generate_html_report() - создание HTML-отчёта
64
+
65
+ 9. Функция ЛР №1 (строки 362-844)
66
+ └── render_lab1() - весь интерфейс ЛР №1
67
+ ├── Загрузка данных
68
+ ├── Предобработка
69
+ ├── Описательная статистика
70
+ ├── Стационарность
71
+ ├── Лаги и скользящие
72
+ ├── ACF/PACF
73
+ ├── Декомпозиция
74
+ └── Генерация отчёта
75
+
76
+ 10. Функция ЛР №2 (строки 846-1646)
77
+ └── render_lab2() - весь интерфейс ЛР №2
78
+ ├── Декомпозиция
79
+ ├── Feature Engineering
80
+ ├── Кросс-валидация
81
+ ├── Преобразования
82
+ ├── Модели экспоненциального сглаживания
83
+ ├── Диагностика остатков
84
+ └── Сравнительный анализ
85
+
86
+ 11. Главный код (строки 1648-1654)
87
+ └── Выбор лабораторной работы через sidebar
88
+ ```
89
+
90
+ ### Поток данных в ЛР №1:
91
+
92
+ ```
93
+ Загрузка CSV
94
+
95
+ preprocess_timeseries()
96
+
97
+ df_clean (очищенные данные)
98
+
99
+ descriptive_statistics() → Таблица статистик
100
+
101
+ run_adf() / run_kpss() → Тесты стационарности
102
+
103
+ create_lags_and_rolls() → Датасет с лагами
104
+
105
+ get_acf_pacf_with_conf() → Графики ACF/PACF
106
+
107
+ seasonal_decompose() → Декомпозиция
108
+
109
+ generate_html_report() → HTML-отчёт
110
+ ```
111
+
112
+ ### Поток данных в ЛР №2:
113
+
114
+ ```
115
+ df_clean (из ЛР №1)
116
+
117
+ Разделение на train/test
118
+
119
+ seasonal_decompose() → Декомпозиция
120
+
121
+ create_advanced_features() → Расширенные признаки (опционально)
122
+
123
+ apply_transformations() → Преобразования к стационарности
124
+
125
+ create_exponential_smoothing_model() → Модель
126
+
127
+ recursive_forecast() / direct_forecast() / hybrid_forecast() → Прогноз
128
+
129
+ inverse_transformations() → Обратное преобразование
130
+
131
+ evaluate_forecast() → Метрики (MAE, RMSE, MAPE)
132
+
133
+ diagnose_model_residuals() → Диагностика остатков
134
+ ```
135
+
136
+ ---
137
+
138
+ ## 🔧 lab2_functions.py - Функции для ЛР №2
139
+
140
+ ### Структура файла:
141
+
142
+ ```
143
+ 1. Метрики (строки 18-25)
144
+ └── calculate_mape() - расчёт MAPE
145
+
146
+ 2. Feature Engineering (строки 28-70)
147
+ └── create_advanced_features() - создание признаков
148
+ ├── Временные признаки (день недели, месяц)
149
+ ├── Циклические признаки (sin/cos)
150
+ ├── Лаги (lag_1, lag_7, lag_30)
151
+ └── Скользящие статистики (mean, std, min, max)
152
+
153
+ 3. Преобразования (строки 73-223)
154
+ ├── apply_boxcox_transform() - преобразование Бокса-Кокса
155
+ ├── inverse_boxcox_transform() - обратное преобразование
156
+ ├── apply_transformations() - цепочка преобразований
157
+ └── inverse_transformations() - обратная цепочка
158
+
159
+ 4. Стратегии прогнозирования (строки 226-413)
160
+ ├── recursive_forecast() - рекурсивная стратегия
161
+ ├── direct_forecast() - прямая стратегия
162
+ └── hybrid_forecast() - гибридная стратегия
163
+
164
+ 5. Модели (строки 416-435)
165
+ └── create_exponential_smoothing_model() - создание модели
166
+
167
+ 6. Оценка качества (строки 438-457)
168
+ ├── evaluate_forecast() - метрики качества
169
+ └── naive_forecast() - наивный прогноз (baseline)
170
+
171
+ 7. Кросс-валидация (строки 460-535)
172
+ ├── time_series_cv_sliding_window() - скользящее окно
173
+ └── time_series_cv_expanding_window() - расширяющееся окно
174
+
175
+ 8. Диагностика (строки 538-602)
176
+ └── diagnose_model_residuals() - диагностика остатков
177
+ ├── Тест Льюнга-Бокса (автокорреляция)
178
+ ├── Тест Шапиро-Уилка (нормальность)
179
+ └── Тесты стационарности (ADF/KPSS)
180
+ ```
181
+
182
+ ### Зависимости между функциями:
183
+
184
+ ```
185
+ apply_transformations()
186
+
187
+ create_exponential_smoothing_model()
188
+
189
+ recursive_forecast() / direct_forecast() / hybrid_forecast()
190
+
191
+ inverse_transformations()
192
+
193
+ evaluate_forecast()
194
+
195
+ diagnose_model_residuals()
196
+ ```
197
+
198
+ ---
199
+
200
+ ## 🔄 Как работает программа
201
+
202
+ ### 1. Запуск приложения
203
+
204
+ ```python
205
+ # streamlit_app.py, строка 1648
206
+ if lab_choice == "ЛР №1: ...":
207
+ render_lab1()
208
+ elif lab_choice == "ЛР №2: ...":
209
+ render_lab2()
210
+ ```
211
+
212
+ ### 2. Выбор лабораторной работы
213
+
214
+ Пользователь выбирает в sidebar (боковая панель):
215
+ - "ЛР №1: Введение в анализ временных рядов"
216
+ - "ЛР №2: Прогнозирование временных рядов"
217
+
218
+ ### 3. Сохранение состояния
219
+
220
+ Программа использует `st.session_state` для сохранения:
221
+ - `df_in` - исходные данные
222
+ - `df_clean` - очищенные данные
223
+ - `df_lags` - данные с лагами
224
+ - `decomp` - результат декомпозиции
225
+ - `forecast_results` - результаты прогнозирования
226
+ - `models` - обученные модели
227
+
228
+ ### 4. Обработка данных
229
+
230
+ Все преобразования применяются последовательно:
231
+ 1. Загрузка → `df_in`
232
+ 2. Предобработка → `df_clean`
233
+ 3. Создание лагов → `df_lags`
234
+ 4. Преобразования → `s_train_transformed`
235
+ 5. Прогнозирование → `forecast`
236
+ 6. Обратное преобразование → `forecast_original`
237
+
238
+ ---
239
+
240
+ ## 📊 Ключевые функции и их назначение
241
+
242
+ ### Предобработка:
243
+
244
+ | Функция | Что делает |
245
+ |---------|------------|
246
+ | `preprocess_timeseries()` | Основная функция очистки данных |
247
+ | `detect_outliers_iqr()` | Находит выбросы методом IQR |
248
+ | `winsorize_series()` | Обрезает экстремальные значения |
249
+
250
+ ### Анализ:
251
+
252
+ | Функция | Что делает |
253
+ |---------|------------|
254
+ | `descriptive_statistics()` | Расчёт статистик (mean, std, etc.) |
255
+ | `run_adf()` | Тест стационарности ADF |
256
+ | `run_kpss()` | Тест стационарности KPSS |
257
+ | `get_acf_pacf_with_conf()` | Расчёт ACF/PACF с доверительными интервалами |
258
+
259
+ ### Прогнозирование:
260
+
261
+ | Функция | Что делает |
262
+ |---------|------------|
263
+ | `apply_transformations()` | Применяет преобразования (log, boxcox, diff) |
264
+ | `create_exponential_smoothing_model()` | Создаёт модель экспоненциального сглаживания |
265
+ | `recursive_forecast()` | Рекурсивная стратегия прогнозирования |
266
+ | `direct_forecast()` | Прямая стратегия прогнозирования |
267
+ | `hybrid_forecast()` | Гибридная стратегия прогнозирования |
268
+ | `inverse_transformations()` | Обратное преобразование прогнозов |
269
+
270
+ ### Оценка:
271
+
272
+ | Функция | Что делает |
273
+ |---------|------------|
274
+ | `evaluate_forecast()` | Расчёт метрик (MAE, RMSE, MAPE) |
275
+ | `diagnose_model_residuals()` | Диагностика остатков модели |
276
+
277
+ ---
278
+
279
+ ## 🎯 Где что искать
280
+
281
+ ### Если нужно изменить предобработку:
282
+ → `streamlit_app.py`, функция `preprocess_timeseries()` (строки 97-195)
283
+
284
+ ### Если нужно добавить новую метрику:
285
+ → `lab2_functions.py`, функция `evaluate_forecast()` (строки 438-451)
286
+
287
+ ### Если нужно изменить визуализацию:
288
+ → `streamlit_app.py`, функции `render_lab1()` или `render_lab2()`
289
+
290
+ ### Если нужно добавить новую модель:
291
+ → `lab2_functions.py`, функция `create_exponential_smoothing_model()` (строки 416-435)
292
+
293
+ ### Если нужно изменить стратегию прогнозирования:
294
+ → `lab2_functions.py`, функции `recursive_forecast()`, `direct_forecast()`, `hybrid_forecast()`
295
+
296
+ ---
297
+
298
+ ## 🔗 Связи между модулями
299
+
300
+ ```
301
+ streamlit_app.py
302
+
303
+ ├── Импортирует функции из lab2_functions.py
304
+ │ ├── create_advanced_features()
305
+ │ ├── apply_transformations()
306
+ │ ├── recursive_forecast()
307
+ │ ├── direct_forecast()
308
+ │ ├── hybrid_forecast()
309
+ │ ├── create_exponential_smoothing_model()
310
+ │ ├── evaluate_forecast()
311
+ │ └── diagnose_model_residuals()
312
+
313
+ └── Использует библиотеки:
314
+ ├── pandas - работа с данными
315
+ ├── numpy - численные вычисления
316
+ ├── streamlit - веб-интерфейс
317
+ ├── plotly - интерактивные графики
318
+ ├── statsmodels - статистические модели
319
+ └── scipy - научные вычисления
320
+ ```
321
+
322
+ ---
323
+
324
+ ## 📝 Примечания
325
+
326
+ 1. **session_state** используется для сохранения состояния между перезагрузками страницы
327
+ 2. **Обработка ошибок** - большинство функций имеют try/except блоки
328
+ 3. **Визуализация** - используется Plotly для интерактивных графиков
329
+ 4. **Модульность** - функции разделены по назначению (предобработка, анализ, прогнозирование)
330
+
331
+ ---
332
+
333
+ **Для подробного понимания работы программы см. `РУКОВОДСТВО.md`**
334
+