Spaces:
No application file
No application file
Kolesnikov Dmitry
commited on
Commit
·
7c4a2f3
1
Parent(s):
b34a74f
debug: Доработки через дипсик
Browse files- _lab3_input_tmp.csv +821 -821
- lab3_report.html +0 -0
- src/lab3_pipeline.py +695 -122
- src/streamlit_app.py +577 -27
- src/streamlit_lab3.py +0 -50
_lab3_input_tmp.csv
CHANGED
|
@@ -1,821 +1,821 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 531 |
-
|
| 532 |
-
|
| 533 |
-
|
| 534 |
-
|
| 535 |
-
|
| 536 |
-
|
| 537 |
-
|
| 538 |
-
|
| 539 |
-
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
| 543 |
-
|
| 544 |
-
|
| 545 |
-
|
| 546 |
-
|
| 547 |
-
|
| 548 |
-
|
| 549 |
-
|
| 550 |
-
|
| 551 |
-
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
|
| 577 |
-
|
| 578 |
-
|
| 579 |
-
|
| 580 |
-
|
| 581 |
-
|
| 582 |
-
|
| 583 |
-
|
| 584 |
-
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
|
| 600 |
-
|
| 601 |
-
|
| 602 |
-
|
| 603 |
-
|
| 604 |
-
|
| 605 |
-
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
|
| 661 |
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
| 751 |
-
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
-
|
| 763 |
-
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
| 820 |
-
|
| 821 |
-
|
|
|
|
| 1 |
+
timestamp,DailyNewCases,ActiveCases,DailyNewDeaths
|
| 2 |
+
2020-02-15 00:00:00+03:00,0.0,0.0,0.0
|
| 3 |
+
2020-02-16 00:00:00+03:00,0.0,0.0,0.0
|
| 4 |
+
2020-02-17 00:00:00+03:00,0.0,0.0,0.0
|
| 5 |
+
2020-02-18 00:00:00+03:00,0.0,0.0,0.0
|
| 6 |
+
2020-02-19 00:00:00+03:00,0.0,0.0,0.0
|
| 7 |
+
2020-02-20 00:00:00+03:00,0.0,0.0,0.0
|
| 8 |
+
2020-02-21 00:00:00+03:00,0.0,0.0,0.0
|
| 9 |
+
2020-02-22 00:00:00+03:00,0.0,0.0,0.0
|
| 10 |
+
2020-02-23 00:00:00+03:00,0.0,0.0,0.0
|
| 11 |
+
2020-02-24 00:00:00+03:00,0.0,0.0,0.0
|
| 12 |
+
2020-02-25 00:00:00+03:00,0.0,0.0,0.0
|
| 13 |
+
2020-02-26 00:00:00+03:00,0.0,0.0,0.0
|
| 14 |
+
2020-02-27 00:00:00+03:00,0.0,0.0,0.0
|
| 15 |
+
2020-02-28 00:00:00+03:00,0.0,0.0,0.0
|
| 16 |
+
2020-02-29 00:00:00+03:00,0.0,0.0,0.0
|
| 17 |
+
2020-03-01 00:00:00+03:00,0.0,0.0,0.0
|
| 18 |
+
2020-03-02 00:00:00+03:00,1.0,1.0,0.0
|
| 19 |
+
2020-03-03 00:00:00+03:00,0.0,1.0,0.0
|
| 20 |
+
2020-03-04 00:00:00+03:00,0.0,1.0,0.0
|
| 21 |
+
2020-03-05 00:00:00+03:00,4.0,5.0,0.0
|
| 22 |
+
2020-03-06 00:00:00+03:00,6.0,11.0,0.0
|
| 23 |
+
2020-03-07 00:00:00+03:00,1.0,11.0,0.0
|
| 24 |
+
2020-03-08 00:00:00+03:00,3.0,14.0,0.0
|
| 25 |
+
2020-03-09 00:00:00+03:00,3.0,17.0,0.0
|
| 26 |
+
2020-03-10 00:00:00+03:00,0.0,17.0,0.0
|
| 27 |
+
2020-03-11 00:00:00+03:00,8.0,25.0,0.0
|
| 28 |
+
2020-03-12 00:00:00+03:00,6.0,31.0,0.0
|
| 29 |
+
2020-03-13 00:00:00+03:00,11.0,37.0,0.0
|
| 30 |
+
2020-03-14 00:00:00+03:00,14.0,51.0,0.0
|
| 31 |
+
2020-03-15 00:00:00+03:00,4.0,55.0,0.0
|
| 32 |
+
2020-03-16 00:00:00+03:00,30.0,85.0,0.0
|
| 33 |
+
2020-03-17 00:00:00+03:00,21.0,106.0,0.0
|
| 34 |
+
2020-03-18 00:00:00+03:00,33.0,139.0,0.0
|
| 35 |
+
2020-03-19 00:00:00+03:00,52.0,190.0,1.0
|
| 36 |
+
2020-03-20 00:00:00+03:00,54.0,240.0,0.0
|
| 37 |
+
2020-03-21 00:00:00+03:00,53.0,289.0,0.0
|
| 38 |
+
2020-03-22 00:00:00+03:00,61.0,350.0,0.0
|
| 39 |
+
2020-03-23 00:00:00+03:00,71.0,420.0,0.0
|
| 40 |
+
2020-03-24 00:00:00+03:00,57.0,472.0,0.0
|
| 41 |
+
2020-03-25 00:00:00+03:00,163.0,626.0,2.0
|
| 42 |
+
2020-03-26 00:00:00+03:00,182.0,799.0,0.0
|
| 43 |
+
2020-03-27 00:00:00+03:00,196.0,987.0,1.0
|
| 44 |
+
2020-03-28 00:00:00+03:00,228.0,1211.0,0.0
|
| 45 |
+
2020-03-29 00:00:00+03:00,270.0,1462.0,4.0
|
| 46 |
+
2020-03-30 00:00:00+03:00,302.0,1761.0,1.0
|
| 47 |
+
2020-03-31 00:00:00+03:00,501.0,2199.0,8.0
|
| 48 |
+
2020-04-01 00:00:00+03:00,440.0,2563.0,7.0
|
| 49 |
+
2020-04-02 00:00:00+03:00,771.0,3283.0,6.0
|
| 50 |
+
2020-04-03 00:00:00+03:00,601.0,3834.0,4.0
|
| 51 |
+
2020-04-04 00:00:00+03:00,582.0,4355.0,9.0
|
| 52 |
+
2020-04-05 00:00:00+03:00,658.0,4989.0,2.0
|
| 53 |
+
2020-04-06 00:00:00+03:00,954.0,5890.0,2.0
|
| 54 |
+
2020-04-07 00:00:00+03:00,1154.0,6945.0,11.0
|
| 55 |
+
2020-04-08 00:00:00+03:00,1175.0,8029.0,5.0
|
| 56 |
+
2020-04-09 00:00:00+03:00,1459.0,9357.0,13.0
|
| 57 |
+
2020-04-10 00:00:00+03:00,1786.0,11028.0,18.0
|
| 58 |
+
2020-04-11 00:00:00+03:00,1667.0,12433.0,12.0
|
| 59 |
+
2020-04-12 00:00:00+03:00,2186.0,14349.0,24.0
|
| 60 |
+
2020-04-13 00:00:00+03:00,2558.0,16710.0,18.0
|
| 61 |
+
2020-04-14 00:00:00+03:00,2774.0,19238.0,22.0
|
| 62 |
+
2020-04-15 00:00:00+03:00,3388.0,22306.0,28.0
|
| 63 |
+
2020-04-16 00:00:00+03:00,3448.0,25402.0,34.0
|
| 64 |
+
2020-04-17 00:00:00+03:00,4070.0,29145.0,41.0
|
| 65 |
+
2020-04-18 00:00:00+03:00,4785.0,33423.0,40.0
|
| 66 |
+
2020-04-19 00:00:00+03:00,6060.0,39201.0,48.0
|
| 67 |
+
2020-04-20 00:00:00+03:00,4268.0,43270.0,44.0
|
| 68 |
+
2020-04-21 00:00:00+03:00,5642.0,48434.0,51.0
|
| 69 |
+
2020-04-22 00:00:00+03:00,5236.0,53066.0,57.0
|
| 70 |
+
2020-04-23 00:00:00+03:00,4774.0,57327.0,42.0
|
| 71 |
+
2020-04-24 00:00:00+03:00,5849.0,62439.0,60.0
|
| 72 |
+
2020-04-25 00:00:00+03:00,5966.0,67657.0,66.0
|
| 73 |
+
2020-04-26 00:00:00+03:00,6361.0,73435.0,66.0
|
| 74 |
+
2020-04-27 00:00:00+03:00,6198.0,79007.0,47.0
|
| 75 |
+
2020-04-28 00:00:00+03:00,6411.0,84235.0,73.0
|
| 76 |
+
2020-04-29 00:00:00+03:00,5841.0,88141.0,105.0
|
| 77 |
+
2020-04-30 00:00:00+03:00,7099.0,93806.0,101.0
|
| 78 |
+
2020-05-01 00:00:00+03:00,7933.0,100042.0,96.0
|
| 79 |
+
2020-05-02 00:00:00+03:00,9623.0,107819.0,53.0
|
| 80 |
+
2020-05-03 00:00:00+03:00,10633.0,116768.0,58.0
|
| 81 |
+
2020-05-04 00:00:00+03:00,10581.0,125817.0,76.0
|
| 82 |
+
2020-05-05 00:00:00+03:00,10102.0,134054.0,95.0
|
| 83 |
+
2020-05-06 00:00:00+03:00,10559.0,143065.0,86.0
|
| 84 |
+
2020-05-07 00:00:00+03:00,11231.0,151732.0,88.0
|
| 85 |
+
2020-05-08 00:00:00+03:00,10699.0,159528.0,98.0
|
| 86 |
+
2020-05-09 00:00:00+03:00,10817.0,164933.0,104.0
|
| 87 |
+
2020-05-10 00:00:00+03:00,11012.0,173467.0,88.0
|
| 88 |
+
2020-05-11 00:00:00+03:00,11656.0,179534.0,94.0
|
| 89 |
+
2020-05-12 00:00:00+03:00,10899.0,186615.0,107.0
|
| 90 |
+
2020-05-13 00:00:00+03:00,10028.0,192056.0,96.0
|
| 91 |
+
2020-05-14 00:00:00+03:00,9974.0,196410.0,93.0
|
| 92 |
+
2020-05-15 00:00:00+03:00,10598.0,202199.0,113.0
|
| 93 |
+
2020-05-16 00:00:00+03:00,9200.0,206340.0,119.0
|
| 94 |
+
2020-05-17 00:00:00+03:00,9709.0,211748.0,94.0
|
| 95 |
+
2020-05-18 00:00:00+03:00,8926.0,217747.0,91.0
|
| 96 |
+
2020-05-19 00:00:00+03:00,9263.0,220974.0,115.0
|
| 97 |
+
2020-05-20 00:00:00+03:00,8764.0,220341.0,135.0
|
| 98 |
+
2020-05-21 00:00:00+03:00,8849.0,221774.0,127.0
|
| 99 |
+
2020-05-22 00:00:00+03:00,8894.0,223374.0,150.0
|
| 100 |
+
2020-05-23 00:00:00+03:00,9434.0,224558.0,139.0
|
| 101 |
+
2020-05-24 00:00:00+03:00,8599.0,227641.0,153.0
|
| 102 |
+
2020-05-25 00:00:00+03:00,8946.0,230996.0,92.0
|
| 103 |
+
2020-05-26 00:00:00+03:00,8915.0,227406.0,174.0
|
| 104 |
+
2020-05-27 00:00:00+03:00,8338.0,224504.0,161.0
|
| 105 |
+
2020-05-28 00:00:00+03:00,8371.0,223916.0,174.0
|
| 106 |
+
2020-05-29 00:00:00+03:00,8572.0,223992.0,232.0
|
| 107 |
+
2020-05-30 00:00:00+03:00,8952.0,224551.0,181.0
|
| 108 |
+
2020-05-31 00:00:00+03:00,9268.0,229267.0,138.0
|
| 109 |
+
2020-06-01 00:00:00+03:00,9035.0,234146.0,162.0
|
| 110 |
+
2020-06-02 00:00:00+03:00,8863.0,231719.0,182.0
|
| 111 |
+
2020-06-03 00:00:00+03:00,8536.0,231105.0,178.0
|
| 112 |
+
2020-06-04 00:00:00+03:00,8831.0,231101.0,169.0
|
| 113 |
+
2020-06-05 00:00:00+03:00,8726.0,231626.0,144.0
|
| 114 |
+
2020-06-06 00:00:00+03:00,8855.0,231576.0,197.0
|
| 115 |
+
2020-06-07 00:00:00+03:00,8984.0,235083.0,134.0
|
| 116 |
+
2020-06-08 00:00:00+03:00,8985.0,239999.0,112.0
|
| 117 |
+
2020-06-09 00:00:00+03:00,8595.0,236714.0,171.0
|
| 118 |
+
2020-06-10 00:00:00+03:00,8404.0,234516.0,216.0
|
| 119 |
+
2020-06-11 00:00:00+03:00,8779.0,234754.0,174.0
|
| 120 |
+
2020-06-12 00:00:00+03:00,8987.0,235338.0,183.0
|
| 121 |
+
2020-06-13 00:00:00+03:00,8706.0,238659.0,114.0
|
| 122 |
+
2020-06-14 00:00:00+03:00,8835.0,241966.0,119.0
|
| 123 |
+
2020-06-15 00:00:00+03:00,8246.0,245580.0,143.0
|
| 124 |
+
2020-06-16 00:00:00+03:00,8248.0,243868.0,193.0
|
| 125 |
+
2020-06-17 00:00:00+03:00,7843.0,241481.0,194.0
|
| 126 |
+
2020-06-18 00:00:00+03:00,7790.0,239468.0,182.0
|
| 127 |
+
2020-06-19 00:00:00+03:00,7972.0,236816.0,181.0
|
| 128 |
+
2020-06-20 00:00:00+03:00,7889.0,234358.0,161.0
|
| 129 |
+
2020-06-21 00:00:00+03:00,7728.0,236858.0,109.0
|
| 130 |
+
2020-06-22 00:00:00+03:00,7600.0,239658.0,95.0
|
| 131 |
+
2020-06-23 00:00:00+03:00,7425.0,234917.0,153.0
|
| 132 |
+
2020-06-24 00:00:00+03:00,7176.0,229546.0,154.0
|
| 133 |
+
2020-06-25 00:00:00+03:00,7113.0,230225.0,92.0
|
| 134 |
+
2020-06-26 00:00:00+03:00,6800.0,227861.0,176.0
|
| 135 |
+
2020-06-27 00:00:00+03:00,6852.0,225325.0,188.0
|
| 136 |
+
2020-06-28 00:00:00+03:00,6791.0,226277.0,104.0
|
| 137 |
+
2020-06-29 00:00:00+03:00,6719.0,228560.0,93.0
|
| 138 |
+
2020-06-30 00:00:00+03:00,6693.0,225879.0,154.0
|
| 139 |
+
2020-07-01 00:00:00+03:00,6556.0,221938.0,216.0
|
| 140 |
+
2020-07-02 00:00:00+03:00,6760.0,222504.0,147.0
|
| 141 |
+
2020-07-03 00:00:00+03:00,6718.0,220131.0,176.0
|
| 142 |
+
2020-07-04 00:00:00+03:00,6632.0,217609.0,168.0
|
| 143 |
+
2020-07-05 00:00:00+03:00,6736.0,220340.0,134.0
|
| 144 |
+
2020-07-06 00:00:00+03:00,6611.0,223237.0,135.0
|
| 145 |
+
2020-07-07 00:00:00+03:00,6368.0,219856.0,198.0
|
| 146 |
+
2020-07-08 00:00:00+03:00,6562.0,217614.0,173.0
|
| 147 |
+
2020-07-09 00:00:00+03:00,6509.0,215142.0,176.0
|
| 148 |
+
2020-07-10 00:00:00+03:00,6635.0,213851.0,174.0
|
| 149 |
+
2020-07-11 00:00:00+03:00,6611.0,211896.0,188.0
|
| 150 |
+
2020-07-12 00:00:00+03:00,6615.0,214766.0,130.0
|
| 151 |
+
2020-07-13 00:00:00+03:00,6537.0,218239.0,104.0
|
| 152 |
+
2020-07-14 00:00:00+03:00,6248.0,215508.0,175.0
|
| 153 |
+
2020-07-15 00:00:00+03:00,6422.0,211350.0,156.0
|
| 154 |
+
2020-07-16 00:00:00+03:00,6428.0,209168.0,167.0
|
| 155 |
+
2020-07-17 00:00:00+03:00,6406.0,207707.0,186.0
|
| 156 |
+
2020-07-18 00:00:00+03:00,6234.0,206327.0,124.0
|
| 157 |
+
2020-07-19 00:00:00+03:00,6109.0,208860.0,95.0
|
| 158 |
+
2020-07-20 00:00:00+03:00,5940.0,211457.0,85.0
|
| 159 |
+
2020-07-21 00:00:00+03:00,5842.0,208364.0,153.0
|
| 160 |
+
2020-07-22 00:00:00+03:00,5862.0,204392.0,165.0
|
| 161 |
+
2020-07-23 00:00:00+03:00,5848.0,201816.0,147.0
|
| 162 |
+
2020-07-24 00:00:00+03:00,5811.0,199029.0,154.0
|
| 163 |
+
2020-07-25 00:00:00+03:00,5871.0,196388.0,146.0
|
| 164 |
+
2020-07-26 00:00:00+03:00,5765.0,198966.0,77.0
|
| 165 |
+
2020-07-27 00:00:00+03:00,5635.0,201437.0,85.0
|
| 166 |
+
2020-07-28 00:00:00+03:00,5395.0,197794.0,150.0
|
| 167 |
+
2020-07-29 00:00:00+03:00,5475.0,194984.0,169.0
|
| 168 |
+
2020-07-30 00:00:00+03:00,5509.0,191042.0,129.0
|
| 169 |
+
2020-07-31 00:00:00+03:00,5482.0,187608.0,161.0
|
| 170 |
+
2020-08-01 00:00:00+03:00,5462.0,184861.0,95.0
|
| 171 |
+
2020-08-02 00:00:00+03:00,5427.0,186569.0,70.0
|
| 172 |
+
2020-08-03 00:00:00+03:00,5394.0,188464.0,79.0
|
| 173 |
+
2020-08-04 00:00:00+03:00,5159.0,185601.0,144.0
|
| 174 |
+
2020-08-05 00:00:00+03:00,5204.0,183111.0,139.0
|
| 175 |
+
2020-08-06 00:00:00+03:00,5267.0,180931.0,116.0
|
| 176 |
+
2020-08-07 00:00:00+03:00,5241.0,178818.0,119.0
|
| 177 |
+
2020-08-08 00:00:00+03:00,5212.0,177286.0,129.0
|
| 178 |
+
2020-08-09 00:00:00+03:00,5189.0,179183.0,77.0
|
| 179 |
+
2020-08-10 00:00:00+03:00,5118.0,180972.0,70.0
|
| 180 |
+
2020-08-11 00:00:00+03:00,4945.0,179293.0,130.0
|
| 181 |
+
2020-08-12 00:00:00+03:00,5102.0,177143.0,129.0
|
| 182 |
+
2020-08-13 00:00:00+03:00,5057.0,175978.0,124.0
|
| 183 |
+
2020-08-14 00:00:00+03:00,5065.0,174361.0,114.0
|
| 184 |
+
2020-08-15 00:00:00+03:00,5061.0,172856.0,119.0
|
| 185 |
+
2020-08-16 00:00:00+03:00,4969.0,174200.0,68.0
|
| 186 |
+
2020-08-17 00:00:00+03:00,4892.0,175904.0,55.0
|
| 187 |
+
2020-08-18 00:00:00+03:00,4748.0,173993.0,132.0
|
| 188 |
+
2020-08-19 00:00:00+03:00,4828.0,171909.0,117.0
|
| 189 |
+
2020-08-20 00:00:00+03:00,4785.0,170494.0,110.0
|
| 190 |
+
2020-08-21 00:00:00+03:00,4870.0,169457.0,90.0
|
| 191 |
+
2020-08-22 00:00:00+03:00,4921.0,168110.0,121.0
|
| 192 |
+
2020-08-23 00:00:00+03:00,4852.0,169727.0,73.0
|
| 193 |
+
2020-08-24 00:00:00+03:00,4744.0,171950.0,65.0
|
| 194 |
+
2020-08-25 00:00:00+03:00,4696.0,169874.0,120.0
|
| 195 |
+
2020-08-26 00:00:00+03:00,4676.0,168032.0,115.0
|
| 196 |
+
2020-08-27 00:00:00+03:00,4711.0,166211.0,121.0
|
| 197 |
+
2020-08-28 00:00:00+03:00,4829.0,165025.0,110.0
|
| 198 |
+
2020-08-29 00:00:00+03:00,4941.0,163938.0,111.0
|
| 199 |
+
2020-08-30 00:00:00+03:00,4980.0,166251.0,68.0
|
| 200 |
+
2020-08-31 00:00:00+03:00,4993.0,168756.0,83.0
|
| 201 |
+
2020-09-01 00:00:00+03:00,4729.0,167044.0,123.0
|
| 202 |
+
2020-09-02 00:00:00+03:00,4952.0,166417.0,115.0
|
| 203 |
+
2020-09-03 00:00:00+03:00,4995.0,165532.0,114.0
|
| 204 |
+
2020-09-04 00:00:00+03:00,5110.0,164709.0,121.0
|
| 205 |
+
2020-09-05 00:00:00+03:00,5205.0,164425.0,110.0
|
| 206 |
+
2020-09-06 00:00:00+03:00,5195.0,166736.0,61.0
|
| 207 |
+
2020-09-07 00:00:00+03:00,5185.0,169542.0,51.0
|
| 208 |
+
2020-09-08 00:00:00+03:00,5099.0,167747.0,122.0
|
| 209 |
+
2020-09-09 00:00:00+03:00,5218.0,166414.0,142.0
|
| 210 |
+
2020-09-10 00:00:00+03:00,5363.0,165734.0,128.0
|
| 211 |
+
2020-09-11 00:00:00+03:00,5504.0,165402.0,102.0
|
| 212 |
+
2020-09-12 00:00:00+03:00,5488.0,165343.0,119.0
|
| 213 |
+
2020-09-13 00:00:00+03:00,5449.0,168008.0,94.0
|
| 214 |
+
2020-09-14 00:00:00+03:00,5509.0,170985.0,57.0
|
| 215 |
+
2020-09-15 00:00:00+03:00,5529.0,170759.0,150.0
|
| 216 |
+
2020-09-16 00:00:00+03:00,5670.0,170488.0,132.0
|
| 217 |
+
2020-09-17 00:00:00+03:00,5762.0,170352.0,144.0
|
| 218 |
+
2020-09-18 00:00:00+03:00,5905.0,170784.0,134.0
|
| 219 |
+
2020-09-19 00:00:00+03:00,6065.0,171450.0,144.0
|
| 220 |
+
2020-09-20 00:00:00+03:00,6148.0,174624.0,79.0
|
| 221 |
+
2020-09-21 00:00:00+03:00,6196.0,178133.0,71.0
|
| 222 |
+
2020-09-22 00:00:00+03:00,6215.0,178212.0,160.0
|
| 223 |
+
2020-09-23 00:00:00+03:00,6431.0,178743.0,150.0
|
| 224 |
+
2020-09-24 00:00:00+03:00,6595.0,179059.0,149.0
|
| 225 |
+
2020-09-25 00:00:00+03:00,7212.0,181846.0,108.0
|
| 226 |
+
2020-09-26 00:00:00+03:00,7523.0,183196.0,169.0
|
| 227 |
+
2020-09-27 00:00:00+03:00,7867.0,187896.0,99.0
|
| 228 |
+
2020-09-28 00:00:00+03:00,8135.0,193268.0,61.0
|
| 229 |
+
2020-09-29 00:00:00+03:00,8232.0,194861.0,160.0
|
| 230 |
+
2020-09-30 00:00:00+03:00,8481.0,197307.0,177.0
|
| 231 |
+
2020-10-01 00:00:00+03:00,8945.0,200098.0,169.0
|
| 232 |
+
2020-10-02 00:00:00+03:00,9412.0,203270.0,186.0
|
| 233 |
+
2020-10-03 00:00:00+03:00,9859.0,207392.0,174.0
|
| 234 |
+
2020-10-04 00:00:00+03:00,10499.0,214500.0,107.0
|
| 235 |
+
2020-10-05 00:00:00+03:00,10888.0,222090.0,117.0
|
| 236 |
+
2020-10-06 00:00:00+03:00,11615.0,227265.0,188.0
|
| 237 |
+
2020-10-07 00:00:00+03:00,11115.0,231479.0,202.0
|
| 238 |
+
2020-10-08 00:00:00+03:00,11493.0,235727.0,191.0
|
| 239 |
+
2020-10-09 00:00:00+03:00,12126.0,240560.0,201.0
|
| 240 |
+
2020-10-10 00:00:00+03:00,12846.0,246428.0,197.0
|
| 241 |
+
2020-10-11 00:00:00+03:00,13634.0,255679.0,143.0
|
| 242 |
+
2020-10-12 00:00:00+03:00,13592.0,265353.0,125.0
|
| 243 |
+
2020-10-13 00:00:00+03:00,13868.0,271427.0,244.0
|
| 244 |
+
2020-10-14 00:00:00+03:00,14231.0,277499.0,239.0
|
| 245 |
+
2020-10-15 00:00:00+03:00,13754.0,282575.0,286.0
|
| 246 |
+
2020-10-16 00:00:00+03:00,15150.0,289008.0,232.0
|
| 247 |
+
2020-10-17 00:00:00+03:00,14922.0,295034.0,279.0
|
| 248 |
+
2020-10-18 00:00:00+03:00,15099.0,304571.0,185.0
|
| 249 |
+
2020-10-19 00:00:00+03:00,15982.0,315046.0,179.0
|
| 250 |
+
2020-10-20 00:00:00+03:00,16319.0,321392.0,269.0
|
| 251 |
+
2020-10-21 00:00:00+03:00,15700.0,325823.0,317.0
|
| 252 |
+
2020-10-22 00:00:00+03:00,15971.0,330076.0,290.0
|
| 253 |
+
2020-10-23 00:00:00+03:00,17340.0,335870.0,283.0
|
| 254 |
+
2020-10-24 00:00:00+03:00,16521.0,340528.0,296.0
|
| 255 |
+
2020-10-25 00:00:00+03:00,16710.0,349305.0,229.0
|
| 256 |
+
2020-10-26 00:00:00+03:00,17347.0,358859.0,219.0
|
| 257 |
+
2020-10-27 00:00:00+03:00,16550.0,362245.0,320.0
|
| 258 |
+
2020-10-28 00:00:00+03:00,16202.0,365740.0,346.0
|
| 259 |
+
2020-10-29 00:00:00+03:00,17717.0,368351.0,366.0
|
| 260 |
+
2020-10-30 00:00:00+03:00,18283.0,371760.0,355.0
|
| 261 |
+
2020-10-31 00:00:00+03:00,18140.0,374712.0,334.0
|
| 262 |
+
2020-11-01 00:00:00+03:00,18665.0,382873.0,245.0
|
| 263 |
+
2020-11-02 00:00:00+03:00,18257.0,390532.0,238.0
|
| 264 |
+
2020-11-03 00:00:00+03:00,18648.0,393494.0,355.0
|
| 265 |
+
2020-11-04 00:00:00+03:00,19768.0,397306.0,389.0
|
| 266 |
+
2020-11-05 00:00:00+03:00,19404.0,404180.0,292.0
|
| 267 |
+
2020-11-06 00:00:00+03:00,20582.0,407429.0,378.0
|
| 268 |
+
2020-11-07 00:00:00+03:00,20396.0,410658.0,364.0
|
| 269 |
+
2020-11-08 00:00:00+03:00,20498.0,419378.0,286.0
|
| 270 |
+
2020-11-09 00:00:00+03:00,21798.0,430198.0,256.0
|
| 271 |
+
2020-11-10 00:00:00+03:00,20977.0,435207.0,368.0
|
| 272 |
+
2020-11-11 00:00:00+03:00,19851.0,436010.0,432.0
|
| 273 |
+
2020-11-12 00:00:00+03:00,21608.0,438368.0,439.0
|
| 274 |
+
2020-11-13 00:00:00+03:00,21983.0,441205.0,411.0
|
| 275 |
+
2020-11-14 00:00:00+03:00,22702.0,444890.0,391.0
|
| 276 |
+
2020-11-15 00:00:00+03:00,22572.0,452654.0,352.0
|
| 277 |
+
2020-11-16 00:00:00+03:00,22778.0,461265.0,303.0
|
| 278 |
+
2020-11-17 00:00:00+03:00,22410.0,461178.0,442.0
|
| 279 |
+
2020-11-18 00:00:00+03:00,20985.0,456528.0,456.0
|
| 280 |
+
2020-11-19 00:00:00+03:00,23610.0,454102.0,463.0
|
| 281 |
+
2020-11-20 00:00:00+03:00,24318.0,453201.0,461.0
|
| 282 |
+
2020-11-21 00:00:00+03:00,24822.0,451535.0,467.0
|
| 283 |
+
2020-11-22 00:00:00+03:00,24581.0,457707.0,401.0
|
| 284 |
+
2020-11-23 00:00:00+03:00,25173.0,466517.0,361.0
|
| 285 |
+
2020-11-24 00:00:00+03:00,24326.0,467126.0,491.0
|
| 286 |
+
2020-11-25 00:00:00+03:00,23675.0,464546.0,507.0
|
| 287 |
+
2020-11-26 00:00:00+03:00,25487.0,464436.0,524.0
|
| 288 |
+
2020-11-27 00:00:00+03:00,27543.0,464801.0,496.0
|
| 289 |
+
2020-11-28 00:00:00+03:00,27100.0,464095.0,510.0
|
| 290 |
+
2020-11-29 00:00:00+03:00,26683.0,468332.0,459.0
|
| 291 |
+
2020-11-30 00:00:00+03:00,26338.0,477055.0,368.0
|
| 292 |
+
2020-12-01 00:00:00+03:00,26402.0,478125.0,569.0
|
| 293 |
+
2020-12-02 00:00:00+03:00,25345.0,475999.0,589.0
|
| 294 |
+
2020-12-03 00:00:00+03:00,28145.0,474088.0,554.0
|
| 295 |
+
2020-12-04 00:00:00+03:00,27403.0,472021.0,569.0
|
| 296 |
+
2020-12-05 00:00:00+03:00,28782.0,472651.0,508.0
|
| 297 |
+
2020-12-06 00:00:00+03:00,29039.0,479891.0,457.0
|
| 298 |
+
2020-12-07 00:00:00+03:00,28142.0,488727.0,456.0
|
| 299 |
+
2020-12-08 00:00:00+03:00,26097.0,489324.0,562.0
|
| 300 |
+
2020-12-09 00:00:00+03:00,26190.0,488689.0,559.0
|
| 301 |
+
2020-12-10 00:00:00+03:00,27927.0,490177.0,562.0
|
| 302 |
+
2020-12-11 00:00:00+03:00,28585.0,491978.0,613.0
|
| 303 |
+
2020-12-12 00:00:00+03:00,28137.0,493437.0,560.0
|
| 304 |
+
2020-12-13 00:00:00+03:00,28080.0,500752.0,488.0
|
| 305 |
+
2020-12-14 00:00:00+03:00,27328.0,509068.0,450.0
|
| 306 |
+
2020-12-15 00:00:00+03:00,26689.0,510367.0,577.0
|
| 307 |
+
2020-12-16 00:00:00+03:00,26509.0,509790.0,596.0
|
| 308 |
+
2020-12-17 00:00:00+03:00,28214.0,510977.0,587.0
|
| 309 |
+
2020-12-18 00:00:00+03:00,28552.0,512825.0,611.0
|
| 310 |
+
2020-12-19 00:00:00+03:00,28209.0,514340.0,585.0
|
| 311 |
+
2020-12-20 00:00:00+03:00,28948.0,521862.0,511.0
|
| 312 |
+
2020-12-21 00:00:00+03:00,29350.0,531014.0,493.0
|
| 313 |
+
2020-12-22 00:00:00+03:00,28776.0,535071.0,561.0
|
| 314 |
+
2020-12-23 00:00:00+03:00,27250.0,537325.0,549.0
|
| 315 |
+
2020-12-24 00:00:00+03:00,29935.0,539735.0,635.0
|
| 316 |
+
2020-12-25 00:00:00+03:00,29018.0,540793.0,563.0
|
| 317 |
+
2020-12-26 00:00:00+03:00,29258.0,541299.0,567.0
|
| 318 |
+
2020-12-27 00:00:00+03:00,28284.0,544641.0,552.0
|
| 319 |
+
2020-12-28 00:00:00+03:00,27787.0,551461.0,487.0
|
| 320 |
+
2020-12-29 00:00:00+03:00,27002.0,553027.0,562.0
|
| 321 |
+
2020-12-30 00:00:00+03:00,26513.0,549706.0,599.0
|
| 322 |
+
2020-12-31 00:00:00+03:00,27747.0,547938.0,593.0
|
| 323 |
+
2021-01-01 00:00:00+03:00,27039.0,548643.0,536.0
|
| 324 |
+
2021-01-02 00:00:00+03:00,26301.0,555600.0,447.0
|
| 325 |
+
2021-01-03 00:00:00+03:00,24150.0,559399.0,504.0
|
| 326 |
+
2021-01-04 00:00:00+03:00,23351.0,561114.0,482.0
|
| 327 |
+
2021-01-05 00:00:00+03:00,24246.0,562210.0,518.0
|
| 328 |
+
2021-01-06 00:00:00+03:00,24217.0,562927.0,445.0
|
| 329 |
+
2021-01-07 00:00:00+03:00,23541.0,562233.0,506.0
|
| 330 |
+
2021-01-08 00:00:00+03:00,23652.0,563754.0,454.0
|
| 331 |
+
2021-01-09 00:00:00+03:00,23309.0,562913.0,470.0
|
| 332 |
+
2021-01-10 00:00:00+03:00,22851.0,561228.0,456.0
|
| 333 |
+
2021-01-11 00:00:00+03:00,23315.0,562321.0,436.0
|
| 334 |
+
2021-01-12 00:00:00+03:00,22934.0,559969.0,531.0
|
| 335 |
+
2021-01-13 00:00:00+03:00,22850.0,553595.0,566.0
|
| 336 |
+
2021-01-14 00:00:00+03:00,24763.0,549832.0,570.0
|
| 337 |
+
2021-01-15 00:00:00+03:00,24715.0,546356.0,555.0
|
| 338 |
+
2021-01-16 00:00:00+03:00,24092.0,542547.0,590.0
|
| 339 |
+
2021-01-17 00:00:00+03:00,23586.0,542212.0,481.0
|
| 340 |
+
2021-01-18 00:00:00+03:00,22857.0,546265.0,471.0
|
| 341 |
+
2021-01-19 00:00:00+03:00,21734.0,544151.0,586.0
|
| 342 |
+
2021-01-20 00:00:00+03:00,21152.0,539416.0,597.0
|
| 343 |
+
2021-01-21 00:00:00+03:00,21887.0,533789.0,612.0
|
| 344 |
+
2021-01-22 00:00:00+03:00,21513.0,527404.0,580.0
|
| 345 |
+
2021-01-23 00:00:00+03:00,20921.0,519987.0,559.0
|
| 346 |
+
2021-01-24 00:00:00+03:00,21127.0,518178.0,491.0
|
| 347 |
+
2021-01-25 00:00:00+03:00,19290.0,518009.0,456.0
|
| 348 |
+
2021-01-26 00:00:00+03:00,18241.0,511888.0,564.0
|
| 349 |
+
2021-01-27 00:00:00+03:00,17741.0,501113.0,594.0
|
| 350 |
+
2021-01-28 00:00:00+03:00,19138.0,492901.0,575.0
|
| 351 |
+
2021-01-29 00:00:00+03:00,19238.0,485401.0,534.0
|
| 352 |
+
2021-01-30 00:00:00+03:00,19032.0,479419.0,512.0
|
| 353 |
+
2021-01-31 00:00:00+03:00,18359.0,477253.0,485.0
|
| 354 |
+
2021-02-01 00:00:00+03:00,17648.0,476295.0,437.0
|
| 355 |
+
2021-02-02 00:00:00+03:00,16643.0,470027.0,539.0
|
| 356 |
+
2021-02-03 00:00:00+03:00,16474.0,461153.0,526.0
|
| 357 |
+
2021-02-04 00:00:00+03:00,16714.0,452800.0,521.0
|
| 358 |
+
2021-02-05 00:00:00+03:00,16688.0,445379.0,527.0
|
| 359 |
+
2021-02-06 00:00:00+03:00,16627.0,438678.0,497.0
|
| 360 |
+
2021-02-07 00:00:00+03:00,16048.0,434410.0,432.0
|
| 361 |
+
2021-02-08 00:00:00+03:00,15916.0,434038.0,407.0
|
| 362 |
+
2021-02-09 00:00:00+03:00,15019.0,426732.0,530.0
|
| 363 |
+
2021-02-10 00:00:00+03:00,14494.0,418115.0,536.0
|
| 364 |
+
2021-02-11 00:00:00+03:00,15038.0,410639.0,553.0
|
| 365 |
+
2021-02-12 00:00:00+03:00,15089.0,404501.0,507.0
|
| 366 |
+
2021-02-13 00:00:00+03:00,14861.0,400095.0,502.0
|
| 367 |
+
2021-02-14 00:00:00+03:00,14185.0,398656.0,430.0
|
| 368 |
+
2021-02-15 00:00:00+03:00,14207.0,398534.0,394.0
|
| 369 |
+
2021-02-16 00:00:00+03:00,13233.0,393681.0,459.0
|
| 370 |
+
2021-02-17 00:00:00+03:00,12828.0,388123.0,467.0
|
| 371 |
+
2021-02-18 00:00:00+03:00,13447.0,382360.0,480.0
|
| 372 |
+
2021-02-19 00:00:00+03:00,13433.0,376686.0,470.0
|
| 373 |
+
2021-02-20 00:00:00+03:00,12953.0,371675.0,480.0
|
| 374 |
+
2021-02-21 00:00:00+03:00,12742.0,367988.0,417.0
|
| 375 |
+
2021-02-22 00:00:00+03:00,12604.0,367312.0,337.0
|
| 376 |
+
2021-02-23 00:00:00+03:00,11823.0,365762.0,417.0
|
| 377 |
+
2021-02-24 00:00:00+03:00,11749.0,364910.0,383.0
|
| 378 |
+
2021-02-25 00:00:00+03:00,11198.0,359560.0,446.0
|
| 379 |
+
2021-02-26 00:00:00+03:00,11086.0,354496.0,428.0
|
| 380 |
+
2021-02-27 00:00:00+03:00,11534.0,349571.0,439.0
|
| 381 |
+
2021-02-28 00:00:00+03:00,11359.0,348160.0,379.0
|
| 382 |
+
2021-03-01 00:00:00+03:00,11571.0,348121.0,333.0
|
| 383 |
+
2021-03-02 00:00:00+03:00,10565.0,343279.0,441.0
|
| 384 |
+
2021-03-03 00:00:00+03:00,10535.0,337668.0,452.0
|
| 385 |
+
2021-03-04 00:00:00+03:00,11385.0,332455.0,475.0
|
| 386 |
+
2021-03-05 00:00:00+03:00,11024.0,327553.0,462.0
|
| 387 |
+
2021-03-06 00:00:00+03:00,11022.0,323107.0,441.0
|
| 388 |
+
2021-03-07 00:00:00+03:00,10595.0,321758.0,368.0
|
| 389 |
+
2021-03-08 00:00:00+03:00,10253.0,321310.0,379.0
|
| 390 |
+
2021-03-09 00:00:00+03:00,9445.0,320488.0,336.0
|
| 391 |
+
2021-03-10 00:00:00+03:00,9079.0,315751.0,466.0
|
| 392 |
+
2021-03-11 00:00:00+03:00,9270.0,310556.0,459.0
|
| 393 |
+
2021-03-12 00:00:00+03:00,9794.0,306368.0,486.0
|
| 394 |
+
2021-03-13 00:00:00+03:00,9908.0,302933.0,475.0
|
| 395 |
+
2021-03-14 00:00:00+03:00,10083.0,303209.0,395.0
|
| 396 |
+
2021-03-15 00:00:00+03:00,9437.0,303975.0,404.0
|
| 397 |
+
2021-03-16 00:00:00+03:00,9393.0,302281.0,443.0
|
| 398 |
+
2021-03-17 00:00:00+03:00,8998.0,300097.0,427.0
|
| 399 |
+
2021-03-18 00:00:00+03:00,9803.0,297379.0,460.0
|
| 400 |
+
2021-03-19 00:00:00+03:00,9699.0,294298.0,443.0
|
| 401 |
+
2021-03-20 00:00:00+03:00,9632.0,292259.0,392.0
|
| 402 |
+
2021-03-21 00:00:00+03:00,9299.0,292444.0,371.0
|
| 403 |
+
2021-03-22 00:00:00+03:00,9284.0,293577.0,361.0
|
| 404 |
+
2021-03-23 00:00:00+03:00,8457.0,290747.0,427.0
|
| 405 |
+
2021-03-24 00:00:00+03:00,8861.0,288852.0,401.0
|
| 406 |
+
2021-03-25 00:00:00+03:00,9221.0,286799.0,393.0
|
| 407 |
+
2021-03-26 00:00:00+03:00,9167.0,284681.0,405.0
|
| 408 |
+
2021-03-27 00:00:00+03:00,8885.0,282842.0,387.0
|
| 409 |
+
2021-03-28 00:00:00+03:00,9088.0,282964.0,336.0
|
| 410 |
+
2021-03-29 00:00:00+03:00,8711.0,284102.0,293.0
|
| 411 |
+
2021-03-30 00:00:00+03:00,8277.0,282382.0,409.0
|
| 412 |
+
2021-03-31 00:00:00+03:00,8275.0,280073.0,408.0
|
| 413 |
+
2021-04-01 00:00:00+03:00,9169.0,278612.0,383.0
|
| 414 |
+
2021-04-02 00:00:00+03:00,8792.0,277172.0,400.0
|
| 415 |
+
2021-04-03 00:00:00+03:00,9021.0,276191.0,384.0
|
| 416 |
+
2021-04-04 00:00:00+03:00,8817.0,276439.0,357.0
|
| 417 |
+
2021-04-05 00:00:00+03:00,8646.0,277690.0,343.0
|
| 418 |
+
2021-04-06 00:00:00+03:00,8328.0,276727.0,389.0
|
| 419 |
+
2021-04-07 00:00:00+03:00,8294.0,275202.0,374.0
|
| 420 |
+
2021-04-08 00:00:00+03:00,8672.0,273951.0,365.0
|
| 421 |
+
2021-04-09 00:00:00+03:00,9150.0,273037.0,402.0
|
| 422 |
+
2021-04-10 00:00:00+03:00,8704.0,271760.0,402.0
|
| 423 |
+
2021-04-11 00:00:00+03:00,8702.0,272895.0,337.0
|
| 424 |
+
2021-04-12 00:00:00+03:00,8320.0,274282.0,277.0
|
| 425 |
+
2021-04-13 00:00:00+03:00,8173.0,272506.0,338.0
|
| 426 |
+
2021-04-14 00:00:00+03:00,8326.0,270986.0,399.0
|
| 427 |
+
2021-04-15 00:00:00+03:00,8944.0,269307.0,398.0
|
| 428 |
+
2021-04-16 00:00:00+03:00,8995.0,268796.0,397.0
|
| 429 |
+
2021-04-17 00:00:00+03:00,9321.0,268887.0,398.0
|
| 430 |
+
2021-04-18 00:00:00+03:00,8632.0,269739.0,389.0
|
| 431 |
+
2021-04-19 00:00:00+03:00,8589.0,271164.0,346.0
|
| 432 |
+
2021-04-20 00:00:00+03:00,8164.0,269318.0,379.0
|
| 433 |
+
2021-04-21 00:00:00+03:00,8271.0,267546.0,399.0
|
| 434 |
+
2021-04-22 00:00:00+03:00,8996.0,267211.0,397.0
|
| 435 |
+
2021-04-23 00:00:00+03:00,8840.0,266246.0,398.0
|
| 436 |
+
2021-04-24 00:00:00+03:00,8828.0,265421.0,399.0
|
| 437 |
+
2021-04-25 00:00:00+03:00,8780.0,266329.0,332.0
|
| 438 |
+
2021-04-26 00:00:00+03:00,8803.0,268145.0,356.0
|
| 439 |
+
2021-04-27 00:00:00+03:00,8053.0,267767.0,392.0
|
| 440 |
+
2021-04-28 00:00:00+03:00,7848.0,266808.0,387.0
|
| 441 |
+
2021-04-29 00:00:00+03:00,9284.0,267286.0,364.0
|
| 442 |
+
2021-04-30 00:00:00+03:00,8731.0,267214.0,397.0
|
| 443 |
+
2021-05-01 00:00:00+03:00,9270.0,267455.0,392.0
|
| 444 |
+
2021-05-02 00:00:00+03:00,8697.0,268471.0,342.0
|
| 445 |
+
2021-05-03 00:00:00+03:00,8489.0,270257.0,336.0
|
| 446 |
+
2021-05-04 00:00:00+03:00,7770.0,270935.0,337.0
|
| 447 |
+
2021-05-05 00:00:00+03:00,7975.0,271044.0,360.0
|
| 448 |
+
2021-05-06 00:00:00+03:00,7639.0,270544.0,351.0
|
| 449 |
+
2021-05-07 00:00:00+03:00,8386.0,270532.0,376.0
|
| 450 |
+
2021-05-08 00:00:00+03:00,8329.0,270236.0,370.0
|
| 451 |
+
2021-05-09 00:00:00+03:00,8419.0,270804.0,334.0
|
| 452 |
+
2021-05-10 00:00:00+03:00,8465.0,272174.0,321.0
|
| 453 |
+
2021-05-11 00:00:00+03:00,8115.0,272951.0,329.0
|
| 454 |
+
2021-05-12 00:00:00+03:00,8217.0,272199.0,355.0
|
| 455 |
+
2021-05-13 00:00:00+03:00,8380.0,270838.0,392.0
|
| 456 |
+
2021-05-14 00:00:00+03:00,9462.0,270151.0,393.0
|
| 457 |
+
2021-05-15 00:00:00+03:00,8790.0,268711.0,364.0
|
| 458 |
+
2021-05-16 00:00:00+03:00,8554.0,268301.0,391.0
|
| 459 |
+
2021-05-17 00:00:00+03:00,9328.0,270108.0,340.0
|
| 460 |
+
2021-05-18 00:00:00+03:00,8183.0,268955.0,364.0
|
| 461 |
+
2021-05-19 00:00:00+03:00,7920.0,266924.0,390.0
|
| 462 |
+
2021-05-20 00:00:00+03:00,9232.0,265777.0,396.0
|
| 463 |
+
2021-05-21 00:00:00+03:00,8937.0,264986.0,378.0
|
| 464 |
+
2021-05-22 00:00:00+03:00,8709.0,263964.0,386.0
|
| 465 |
+
2021-05-23 00:00:00+03:00,8951.0,265261.0,357.0
|
| 466 |
+
2021-05-24 00:00:00+03:00,8406.0,266898.0,319.0
|
| 467 |
+
2021-05-25 00:00:00+03:00,7884.0,265646.0,393.0
|
| 468 |
+
2021-05-26 00:00:00+03:00,8373.0,264478.0,406.0
|
| 469 |
+
2021-05-27 00:00:00+03:00,9039.0,263356.0,402.0
|
| 470 |
+
2021-05-28 00:00:00+03:00,9252.0,262819.0,404.0
|
| 471 |
+
2021-05-29 00:00:00+03:00,9289.0,262457.0,401.0
|
| 472 |
+
2021-05-30 00:00:00+03:00,9694.0,264410.0,355.0
|
| 473 |
+
2021-05-31 00:00:00+03:00,8475.0,265831.0,339.0
|
| 474 |
+
2021-06-01 00:00:00+03:00,9500.0,265965.0,372.0
|
| 475 |
+
2021-06-02 00:00:00+03:00,8832.0,265383.0,394.0
|
| 476 |
+
2021-06-03 00:00:00+03:00,8933.0,264540.0,393.0
|
| 477 |
+
2021-06-04 00:00:00+03:00,8947.0,264580.0,377.0
|
| 478 |
+
2021-06-05 00:00:00+03:00,9145.0,264761.0,399.0
|
| 479 |
+
2021-06-06 00:00:00+03:00,9163.0,266204.0,351.0
|
| 480 |
+
2021-06-07 00:00:00+03:00,9429.0,268547.0,330.0
|
| 481 |
+
2021-06-08 00:00:00+03:00,9977.0,269262.0,379.0
|
| 482 |
+
2021-06-09 00:00:00+03:00,10407.0,269456.0,399.0
|
| 483 |
+
2021-06-10 00:00:00+03:00,11699.0,270676.0,383.0
|
| 484 |
+
2021-06-11 00:00:00+03:00,12505.0,272597.0,396.0
|
| 485 |
+
2021-06-12 00:00:00+03:00,13510.0,275722.0,399.0
|
| 486 |
+
2021-06-13 00:00:00+03:00,14723.0,280922.0,357.0
|
| 487 |
+
2021-06-14 00:00:00+03:00,13721.0,285960.0,371.0
|
| 488 |
+
2021-06-15 00:00:00+03:00,14185.0,291169.0,379.0
|
| 489 |
+
2021-06-16 00:00:00+03:00,13397.0,293914.0,396.0
|
| 490 |
+
2021-06-17 00:00:00+03:00,14057.0,296350.0,416.0
|
| 491 |
+
2021-06-18 00:00:00+03:00,17262.0,302205.0,453.0
|
| 492 |
+
2021-06-19 00:00:00+03:00,17906.0,308961.0,466.0
|
| 493 |
+
2021-06-20 00:00:00+03:00,17611.0,317493.0,450.0
|
| 494 |
+
2021-06-21 00:00:00+03:00,17378.0,326070.0,440.0
|
| 495 |
+
2021-06-22 00:00:00+03:00,16715.0,331122.0,546.0
|
| 496 |
+
2021-06-23 00:00:00+03:00,17594.0,335508.0,548.0
|
| 497 |
+
2021-06-24 00:00:00+03:00,20182.0,341617.0,568.0
|
| 498 |
+
2021-06-25 00:00:00+03:00,20393.0,347385.0,601.0
|
| 499 |
+
2021-06-26 00:00:00+03:00,21665.0,354084.0,619.0
|
| 500 |
+
2021-06-27 00:00:00+03:00,20538.0,361295.0,599.0
|
| 501 |
+
2021-06-28 00:00:00+03:00,21650.0,369708.0,611.0
|
| 502 |
+
2021-06-29 00:00:00+03:00,20616.0,374975.0,652.0
|
| 503 |
+
2021-06-30 00:00:00+03:00,21042.0,378992.0,669.0
|
| 504 |
+
2021-07-01 00:00:00+03:00,23543.0,384935.0,672.0
|
| 505 |
+
2021-07-02 00:00:00+03:00,23218.0,389277.0,679.0
|
| 506 |
+
2021-07-03 00:00:00+03:00,24439.0,395120.0,697.0
|
| 507 |
+
2021-07-04 00:00:00+03:00,25142.0,404115.0,663.0
|
| 508 |
+
2021-07-05 00:00:00+03:00,24353.0,413274.0,654.0
|
| 509 |
+
2021-07-06 00:00:00+03:00,23378.0,417504.0,737.0
|
| 510 |
+
2021-07-07 00:00:00+03:00,23962.0,420674.0,725.0
|
| 511 |
+
2021-07-08 00:00:00+03:00,24818.0,423422.0,734.0
|
| 512 |
+
2021-07-09 00:00:00+03:00,25766.0,426630.0,726.0
|
| 513 |
+
2021-07-10 00:00:00+03:00,25082.0,433210.0,752.0
|
| 514 |
+
2021-07-11 00:00:00+03:00,25033.0,440112.0,749.0
|
| 515 |
+
2021-07-12 00:00:00+03:00,25140.0,448113.0,710.0
|
| 516 |
+
2021-07-13 00:00:00+03:00,24702.0,452469.0,780.0
|
| 517 |
+
2021-07-14 00:00:00+03:00,23827.0,454241.0,786.0
|
| 518 |
+
2021-07-15 00:00:00+03:00,25293.0,457250.0,791.0
|
| 519 |
+
2021-07-16 00:00:00+03:00,25704.0,460223.0,799.0
|
| 520 |
+
2021-07-17 00:00:00+03:00,25116.0,463115.0,787.0
|
| 521 |
+
2021-07-18 00:00:00+03:00,25018.0,468483.0,764.0
|
| 522 |
+
2021-07-19 00:00:00+03:00,24633.0,473633.0,719.0
|
| 523 |
+
2021-07-20 00:00:00+03:00,23770.0,474401.0,784.0
|
| 524 |
+
2021-07-21 00:00:00+03:00,23704.0,474738.0,783.0
|
| 525 |
+
2021-07-22 00:00:00+03:00,24471.0,475753.0,796.0
|
| 526 |
+
2021-07-23 00:00:00+03:00,23811.0,476222.0,795.0
|
| 527 |
+
2021-07-24 00:00:00+03:00,23947.0,477418.0,799.0
|
| 528 |
+
2021-07-25 00:00:00+03:00,24072.0,482033.0,779.0
|
| 529 |
+
2021-07-26 00:00:00+03:00,23239.0,488345.0,727.0
|
| 530 |
+
2021-07-27 00:00:00+03:00,23032.0,490482.0,779.0
|
| 531 |
+
2021-07-28 00:00:00+03:00,22420.0,491525.0,798.0
|
| 532 |
+
2021-07-29 00:00:00+03:00,23270.0,493162.0,799.0
|
| 533 |
+
2021-07-30 00:00:00+03:00,23564.0,495447.0,794.0
|
| 534 |
+
2021-07-31 00:00:00+03:00,23807.0,498691.0,792.0
|
| 535 |
+
2021-08-01 00:00:00+03:00,22804.0,503435.0,789.0
|
| 536 |
+
2021-08-02 00:00:00+03:00,23508.0,511265.0,785.0
|
| 537 |
+
2021-08-03 00:00:00+03:00,22010.0,513524.0,788.0
|
| 538 |
+
2021-08-04 00:00:00+03:00,22589.0,515227.0,790.0
|
| 539 |
+
2021-08-05 00:00:00+03:00,23120.0,517183.0,794.0
|
| 540 |
+
2021-08-06 00:00:00+03:00,22660.0,518910.0,792.0
|
| 541 |
+
2021-08-07 00:00:00+03:00,22320.0,520952.0,793.0
|
| 542 |
+
2021-08-08 00:00:00+03:00,22866.0,527362.0,787.0
|
| 543 |
+
2021-08-09 00:00:00+03:00,22160.0,534279.0,769.0
|
| 544 |
+
2021-08-10 00:00:00+03:00,21378.0,536136.0,792.0
|
| 545 |
+
2021-08-11 00:00:00+03:00,21571.0,536841.0,799.0
|
| 546 |
+
2021-08-12 00:00:00+03:00,21932.0,537770.0,808.0
|
| 547 |
+
2021-08-13 00:00:00+03:00,22277.0,539864.0,815.0
|
| 548 |
+
2021-08-14 00:00:00+03:00,22144.0,541639.0,819.0
|
| 549 |
+
2021-08-15 00:00:00+03:00,21624.0,546021.0,816.0
|
| 550 |
+
2021-08-16 00:00:00+03:00,20765.0,550379.0,806.0
|
| 551 |
+
2021-08-17 00:00:00+03:00,20958.0,552125.0,805.0
|
| 552 |
+
2021-08-18 00:00:00+03:00,20914.0,551527.0,799.0
|
| 553 |
+
2021-08-19 00:00:00+03:00,21058.0,547777.0,791.0
|
| 554 |
+
2021-08-20 00:00:00+03:00,20992.0,547633.0,785.0
|
| 555 |
+
2021-08-21 00:00:00+03:00,21000.0,547189.0,797.0
|
| 556 |
+
2021-08-22 00:00:00+03:00,20564.0,551577.0,762.0
|
| 557 |
+
2021-08-23 00:00:00+03:00,19454.0,554854.0,776.0
|
| 558 |
+
2021-08-24 00:00:00+03:00,18833.0,554257.0,794.0
|
| 559 |
+
2021-08-25 00:00:00+03:00,19536.0,553330.0,809.0
|
| 560 |
+
2021-08-26 00:00:00+03:00,19630.0,552479.0,820.0
|
| 561 |
+
2021-08-27 00:00:00+03:00,19509.0,551973.0,798.0
|
| 562 |
+
2021-08-28 00:00:00+03:00,19492.0,551255.0,799.0
|
| 563 |
+
2021-08-29 00:00:00+03:00,19286.0,552940.0,797.0
|
| 564 |
+
2021-08-30 00:00:00+03:00,18325.0,556293.0,792.0
|
| 565 |
+
2021-08-31 00:00:00+03:00,17813.0,554687.0,795.0
|
| 566 |
+
2021-09-01 00:00:00+03:00,18368.0,553940.0,790.0
|
| 567 |
+
2021-09-02 00:00:00+03:00,18985.0,553458.0,798.0
|
| 568 |
+
2021-09-03 00:00:00+03:00,18856.0,552825.0,799.0
|
| 569 |
+
2021-09-04 00:00:00+03:00,18780.0,552072.0,796.0
|
| 570 |
+
2021-09-05 00:00:00+03:00,18645.0,554668.0,793.0
|
| 571 |
+
2021-09-06 00:00:00+03:00,17856.0,557458.0,790.0
|
| 572 |
+
2021-09-07 00:00:00+03:00,17425.0,556845.0,795.0
|
| 573 |
+
2021-09-08 00:00:00+03:00,18024.0,555810.0,797.0
|
| 574 |
+
2021-09-09 00:00:00+03:00,18380.0,553757.0,794.0
|
| 575 |
+
2021-09-10 00:00:00+03:00,18341.0,554188.0,789.0
|
| 576 |
+
2021-09-11 00:00:00+03:00,18891.0,554395.0,796.0
|
| 577 |
+
2021-09-12 00:00:00+03:00,18554.0,557664.0,788.0
|
| 578 |
+
2021-09-13 00:00:00+03:00,18178.0,562654.0,719.0
|
| 579 |
+
2021-09-14 00:00:00+03:00,17837.0,563803.0,781.0
|
| 580 |
+
2021-09-15 00:00:00+03:00,18841.0,564813.0,792.0
|
| 581 |
+
2021-09-16 00:00:00+03:00,19594.0,566287.0,794.0
|
| 582 |
+
2021-09-17 00:00:00+03:00,19905.0,568782.0,791.0
|
| 583 |
+
2021-09-18 00:00:00+03:00,20329.0,572065.0,799.0
|
| 584 |
+
2021-09-19 00:00:00+03:00,20174.0,578028.0,793.0
|
| 585 |
+
2021-09-20 00:00:00+03:00,19744.0,585002.0,778.0
|
| 586 |
+
2021-09-21 00:00:00+03:00,19179.0,587932.0,812.0
|
| 587 |
+
2021-09-22 00:00:00+03:00,19706.0,590719.0,817.0
|
| 588 |
+
2021-09-23 00:00:00+03:00,21438.0,594770.0,820.0
|
| 589 |
+
2021-09-24 00:00:00+03:00,21379.0,599493.0,828.0
|
| 590 |
+
2021-09-25 00:00:00+03:00,22041.0,604387.0,822.0
|
| 591 |
+
2021-09-26 00:00:00+03:00,22498.0,612409.0,805.0
|
| 592 |
+
2021-09-27 00:00:00+03:00,22236.0,620353.0,779.0
|
| 593 |
+
2021-09-28 00:00:00+03:00,21559.0,623692.0,852.0
|
| 594 |
+
2021-09-29 00:00:00+03:00,22430.0,626809.0,857.0
|
| 595 |
+
2021-09-30 00:00:00+03:00,23888.0,631004.0,867.0
|
| 596 |
+
2021-10-01 00:00:00+03:00,24522.0,634684.0,887.0
|
| 597 |
+
2021-10-02 00:00:00+03:00,25219.0,641165.0,886.0
|
| 598 |
+
2021-10-03 00:00:00+03:00,25769.0,650653.0,890.0
|
| 599 |
+
2021-10-04 00:00:00+03:00,25781.0,661025.0,883.0
|
| 600 |
+
2021-10-05 00:00:00+03:00,25110.0,666672.0,895.0
|
| 601 |
+
2021-10-06 00:00:00+03:00,25133.0,671035.0,929.0
|
| 602 |
+
2021-10-07 00:00:00+03:00,27550.0,677331.0,924.0
|
| 603 |
+
2021-10-08 00:00:00+03:00,27246.0,683075.0,936.0
|
| 604 |
+
2021-10-09 00:00:00+03:00,29362.0,690420.0,968.0
|
| 605 |
+
2021-10-10 00:00:00+03:00,28647.0,700831.0,962.0
|
| 606 |
+
2021-10-11 00:00:00+03:00,29409.0,713823.0,957.0
|
| 607 |
+
2021-10-12 00:00:00+03:00,28190.0,720334.0,973.0
|
| 608 |
+
2021-10-13 00:00:00+03:00,28717.0,726266.0,984.0
|
| 609 |
+
2021-10-14 00:00:00+03:00,31299.0,734909.0,986.0
|
| 610 |
+
2021-10-15 00:00:00+03:00,32196.0,743839.0,998.0
|
| 611 |
+
2021-10-16 00:00:00+03:00,33208.0,754162.0,1002.0
|
| 612 |
+
2021-10-17 00:00:00+03:00,34303.0,768751.0,997.0
|
| 613 |
+
2021-10-18 00:00:00+03:00,34325.0,785647.0,998.0
|
| 614 |
+
2021-10-19 00:00:00+03:00,33740.0,794946.0,1015.0
|
| 615 |
+
2021-10-20 00:00:00+03:00,34073.0,802760.0,1028.0
|
| 616 |
+
2021-10-21 00:00:00+03:00,36339.0,812168.0,1036.0
|
| 617 |
+
2021-10-22 00:00:00+03:00,37141.0,822792.0,1064.0
|
| 618 |
+
2021-10-23 00:00:00+03:00,37678.0,833318.0,1075.0
|
| 619 |
+
2021-10-24 00:00:00+03:00,35660.0,845122.0,1072.0
|
| 620 |
+
2021-10-25 00:00:00+03:00,37930.0,861293.0,1069.0
|
| 621 |
+
2021-10-26 00:00:00+03:00,36446.0,869660.0,1106.0
|
| 622 |
+
2021-10-27 00:00:00+03:00,36582.0,875968.0,1123.0
|
| 623 |
+
2021-10-28 00:00:00+03:00,40096.0,885587.0,1159.0
|
| 624 |
+
2021-10-29 00:00:00+03:00,39849.0,893811.0,1163.0
|
| 625 |
+
2021-10-30 00:00:00+03:00,40251.0,903993.0,1160.0
|
| 626 |
+
2021-10-31 00:00:00+03:00,40993.0,916713.0,1158.0
|
| 627 |
+
2021-11-01 00:00:00+03:00,40402.0,932773.0,1155.0
|
| 628 |
+
2021-11-02 00:00:00+03:00,39008.0,939698.0,1178.0
|
| 629 |
+
2021-11-03 00:00:00+03:00,40443.0,946145.0,1189.0
|
| 630 |
+
2021-11-04 00:00:00+03:00,40217.0,953239.0,1195.0
|
| 631 |
+
2021-11-05 00:00:00+03:00,40735.0,964177.0,1192.0
|
| 632 |
+
2021-11-06 00:00:00+03:00,41335.0,975123.0,1188.0
|
| 633 |
+
2021-11-07 00:00:00+03:00,39165.0,986303.0,1179.0
|
| 634 |
+
2021-11-08 00:00:00+03:00,39400.0,998931.0,1190.0
|
| 635 |
+
2021-11-09 00:00:00+03:00,39160.0,1004844.0,1211.0
|
| 636 |
+
2021-11-10 00:00:00+03:00,38058.0,1007098.0,1239.0
|
| 637 |
+
2021-11-11 00:00:00+03:00,40759.0,1013464.0,1237.0
|
| 638 |
+
2021-11-12 00:00:00+03:00,40123.0,1018707.0,1235.0
|
| 639 |
+
2021-11-13 00:00:00+03:00,39256.0,1022920.0,1241.0
|
| 640 |
+
2021-11-14 00:00:00+03:00,38823.0,1030703.0,1219.0
|
| 641 |
+
2021-11-15 00:00:00+03:00,38420.0,1040210.0,1211.0
|
| 642 |
+
2021-11-16 00:00:00+03:00,36818.0,1041627.0,1240.0
|
| 643 |
+
2021-11-17 00:00:00+03:00,36626.0,1040618.0,1247.0
|
| 644 |
+
2021-11-18 00:00:00+03:00,37374.0,1040327.0,1251.0
|
| 645 |
+
2021-11-19 00:00:00+03:00,37156.0,1039225.0,1254.0
|
| 646 |
+
2021-11-20 00:00:00+03:00,37120.0,1038919.0,1254.0
|
| 647 |
+
2021-11-21 00:00:00+03:00,36970.0,1042133.0,1252.0
|
| 648 |
+
2021-11-22 00:00:00+03:00,35681.0,1047860.0,1241.0
|
| 649 |
+
2021-11-23 00:00:00+03:00,33996.0,1044562.0,1243.0
|
| 650 |
+
2021-11-24 00:00:00+03:00,33558.0,1040198.0,1240.0
|
| 651 |
+
2021-11-25 00:00:00+03:00,33796.0,1034306.0,1238.0
|
| 652 |
+
2021-11-26 00:00:00+03:00,34690.0,1031616.0,1235.0
|
| 653 |
+
2021-11-27 00:00:00+03:00,33946.0,1027829.0,1239.0
|
| 654 |
+
2021-11-28 00:00:00+03:00,33548.0,1029507.0,1224.0
|
| 655 |
+
2021-11-29 00:00:00+03:00,33860.0,1034458.0,1209.0
|
| 656 |
+
2021-11-30 00:00:00+03:00,32648.0,1032435.0,1229.0
|
| 657 |
+
2021-12-01 00:00:00+03:00,32837.0,1028367.0,1226.0
|
| 658 |
+
2021-12-02 00:00:00+03:00,33389.0,1025350.0,1221.0
|
| 659 |
+
2021-12-03 00:00:00+03:00,32930.0,1020549.0,1217.0
|
| 660 |
+
2021-12-04 00:00:00+03:00,32974.0,1017126.0,1215.0
|
| 661 |
+
2021-12-05 00:00:00+03:00,32602.0,1017929.0,1206.0
|
| 662 |
+
2021-12-06 00:00:00+03:00,32136.0,1020811.0,1184.0
|
| 663 |
+
2021-12-07 00:00:00+03:00,31096.0,1016110.0,1182.0
|
| 664 |
+
2021-12-08 00:00:00+03:00,30752.0,1008707.0,1179.0
|
| 665 |
+
2021-12-09 00:00:00+03:00,30209.0,1001941.0,1181.0
|
| 666 |
+
2021-12-10 00:00:00+03:00,30873.0,995981.0,1176.0
|
| 667 |
+
2021-12-11 00:00:00+03:00,30288.0,988652.0,1171.0
|
| 668 |
+
2021-12-12 00:00:00+03:00,29929.0,986058.0,1132.0
|
| 669 |
+
2021-12-13 00:00:00+03:00,29558.0,985934.0,1121.0
|
| 670 |
+
2021-12-14 00:00:00+03:00,28343.0,979048.0,1145.0
|
| 671 |
+
2021-12-15 00:00:00+03:00,28363.0,970636.0,1142.0
|
| 672 |
+
2021-12-16 00:00:00+03:00,28486.0,960834.0,1133.0
|
| 673 |
+
2021-12-17 00:00:00+03:00,27743.0,950060.0,1080.0
|
| 674 |
+
2021-12-18 00:00:00+03:00,27434.0,938377.0,1076.0
|
| 675 |
+
2021-12-19 00:00:00+03:00,27967.0,932666.0,1023.0
|
| 676 |
+
2021-12-20 00:00:00+03:00,27022.0,928610.0,1019.0
|
| 677 |
+
2021-12-21 00:00:00+03:00,25907.0,913271.0,1027.0
|
| 678 |
+
2021-12-22 00:00:00+03:00,25264.0,895193.0,1020.0
|
| 679 |
+
2021-12-23 00:00:00+03:00,25667.0,878213.0,1002.0
|
| 680 |
+
2021-12-24 00:00:00+03:00,24703.0,860705.0,998.0
|
| 681 |
+
2021-12-25 00:00:00+03:00,24946.0,842563.0,981.0
|
| 682 |
+
2021-12-26 00:00:00+03:00,23721.0,828031.0,968.0
|
| 683 |
+
2021-12-27 00:00:00+03:00,23210.0,816589.0,937.0
|
| 684 |
+
2021-12-28 00:00:00+03:00,21922.0,793615.0,935.0
|
| 685 |
+
2021-12-29 00:00:00+03:00,21119.0,771026.0,932.0
|
| 686 |
+
2021-12-30 00:00:00+03:00,21073.0,748169.0,926.0
|
| 687 |
+
2021-12-31 00:00:00+03:00,20638.0,727203.0,912.0
|
| 688 |
+
2022-01-01 00:00:00+03:00,19751.0,712963.0,847.0
|
| 689 |
+
2022-01-02 00:00:00+03:00,18233.0,703409.0,811.0
|
| 690 |
+
2022-01-03 00:00:00+03:00,16343.0,694880.0,835.0
|
| 691 |
+
2022-01-04 00:00:00+03:00,15903.0,682878.0,834.0
|
| 692 |
+
2022-01-05 00:00:00+03:00,15772.0,672241.0,828.0
|
| 693 |
+
2022-01-06 00:00:00+03:00,15316.0,663806.0,802.0
|
| 694 |
+
2022-01-07 00:00:00+03:00,16735.0,657719.0,787.0
|
| 695 |
+
2022-01-08 00:00:00+03:00,16568.0,653042.0,796.0
|
| 696 |
+
2022-01-09 00:00:00+03:00,16246.0,647774.0,763.0
|
| 697 |
+
2022-01-10 00:00:00+03:00,15830.0,642973.0,741.0
|
| 698 |
+
2022-01-11 00:00:00+03:00,17525.0,634499.0,783.0
|
| 699 |
+
2022-01-12 00:00:00+03:00,17946.0,625354.0,745.0
|
| 700 |
+
2022-01-13 00:00:00+03:00,21155.0,619785.0,740.0
|
| 701 |
+
2022-01-14 00:00:00+03:00,23820.0,617914.0,739.0
|
| 702 |
+
2022-01-15 00:00:00+03:00,27179.0,617786.0,723.0
|
| 703 |
+
2022-01-16 00:00:00+03:00,29230.0,623599.0,686.0
|
| 704 |
+
2022-01-17 00:00:00+03:00,30726.0,633899.0,670.0
|
| 705 |
+
2022-01-18 00:00:00+03:00,31252.0,639899.0,688.0
|
| 706 |
+
2022-01-19 00:00:00+03:00,33899.0,650180.0,698.0
|
| 707 |
+
2022-01-20 00:00:00+03:00,38850.0,663868.0,684.0
|
| 708 |
+
2022-01-21 00:00:00+03:00,49513.0,687970.0,692.0
|
| 709 |
+
2022-01-22 00:00:00+03:00,49485.82,718976.0,681.0
|
| 710 |
+
2022-01-23 00:00:00+03:00,49458.64,758457.0,679.0
|
| 711 |
+
2022-01-24 00:00:00+03:00,49431.46,801197.0,655.0
|
| 712 |
+
2022-01-25 00:00:00+03:00,49404.28,841921.0,681.0
|
| 713 |
+
2022-01-26 00:00:00+03:00,49377.1,887759.0,657.0
|
| 714 |
+
2022-01-27 00:00:00+03:00,49349.92,946156.0,665.0
|
| 715 |
+
2022-01-28 00:00:00+03:00,49322.74,1014017.0,673.0
|
| 716 |
+
2022-01-29 00:00:00+03:00,49295.56,1014783.3,668.0
|
| 717 |
+
2022-01-30 00:00:00+03:00,49268.38,1015549.6,617.0
|
| 718 |
+
2022-01-31 00:00:00+03:00,49241.2,1016315.9,621.0
|
| 719 |
+
2022-02-01 00:00:00+03:00,49214.02,1017082.2,663.0
|
| 720 |
+
2022-02-02 00:00:00+03:00,49186.84,1017848.5,678.0
|
| 721 |
+
2022-02-03 00:00:00+03:00,49159.66,1018614.8,667.0
|
| 722 |
+
2022-02-04 00:00:00+03:00,49132.48,1019381.1,682.0
|
| 723 |
+
2022-02-05 00:00:00+03:00,49105.3,1020147.4,714.0
|
| 724 |
+
2022-02-06 00:00:00+03:00,49078.12,1020913.7,661.0
|
| 725 |
+
2022-02-07 00:00:00+03:00,49050.94,1021680.0,609.0
|
| 726 |
+
2022-02-08 00:00:00+03:00,49023.76,1022446.3,698.0
|
| 727 |
+
2022-02-09 00:00:00+03:00,48996.58,1023212.6,669.0
|
| 728 |
+
2022-02-10 00:00:00+03:00,48969.4,1023978.9,701.0
|
| 729 |
+
2022-02-11 00:00:00+03:00,48942.22,1024745.2,722.0
|
| 730 |
+
2022-02-12 00:00:00+03:00,48915.04,1025511.5,729.0
|
| 731 |
+
2022-02-13 00:00:00+03:00,48887.86,1026277.8,706.0
|
| 732 |
+
2022-02-14 00:00:00+03:00,48860.68,1027044.1,683.0
|
| 733 |
+
2022-02-15 00:00:00+03:00,48833.5,1027810.4,704.0
|
| 734 |
+
2022-02-16 00:00:00+03:00,48806.32,1028576.7,748.0
|
| 735 |
+
2022-02-17 00:00:00+03:00,48779.14,1029343.0,790.0
|
| 736 |
+
2022-02-18 00:00:00+03:00,48751.96,1030109.3,784.0
|
| 737 |
+
2022-02-19 00:00:00+03:00,48724.78,1030875.6,798.0
|
| 738 |
+
2022-02-20 00:00:00+03:00,48697.6,1031641.9,745.0
|
| 739 |
+
2022-02-21 00:00:00+03:00,48670.42,1032408.2,735.0
|
| 740 |
+
2022-02-22 00:00:00+03:00,48643.24,1033174.5,796.0
|
| 741 |
+
2022-02-23 00:00:00+03:00,48616.06,1033940.8,785.0
|
| 742 |
+
2022-02-24 00:00:00+03:00,48588.88,1034707.1,762.0
|
| 743 |
+
2022-02-25 00:00:00+03:00,48561.7,1035473.4,787.0
|
| 744 |
+
2022-02-26 00:00:00+03:00,48534.52,1036239.7,793.0
|
| 745 |
+
2022-02-27 00:00:00+03:00,48507.34,1037006.0,769.0
|
| 746 |
+
2022-02-28 00:00:00+03:00,48480.16,1037772.3,733.0
|
| 747 |
+
2022-03-01 00:00:00+03:00,48452.98,1038538.6,786.0
|
| 748 |
+
2022-03-02 00:00:00+03:00,48425.8,1039304.9,784.0
|
| 749 |
+
2022-03-03 00:00:00+03:00,48398.62,1040071.2,781.0
|
| 750 |
+
2022-03-04 00:00:00+03:00,48371.44,1040837.5,776.0
|
| 751 |
+
2022-03-05 00:00:00+03:00,48344.26,1041603.8,750.0
|
| 752 |
+
2022-03-06 00:00:00+03:00,48317.08,1042370.1,744.0
|
| 753 |
+
2022-03-07 00:00:00+03:00,48289.9,1043136.4,668.0
|
| 754 |
+
2022-03-08 00:00:00+03:00,48262.72,1043902.7,652.0
|
| 755 |
+
2022-03-09 00:00:00+03:00,48235.54,1044669.0,645.0
|
| 756 |
+
2022-03-10 00:00:00+03:00,48208.36,1045435.3,665.0
|
| 757 |
+
2022-03-11 00:00:00+03:00,48181.18,1046201.6,674.0
|
| 758 |
+
2022-03-12 00:00:00+03:00,48154.0,1046967.9,630.0
|
| 759 |
+
2022-03-13 00:00:00+03:00,44989.0,1047734.2,596.0
|
| 760 |
+
2022-03-14 00:00:00+03:00,41055.0,1048500.5,533.0
|
| 761 |
+
2022-03-15 00:00:00+03:00,36678.0,1049266.8,558.0
|
| 762 |
+
2022-03-16 00:00:00+03:00,36519.0,1050033.1,576.0
|
| 763 |
+
2022-03-17 00:00:00+03:00,34819.0,1050799.4,561.0
|
| 764 |
+
2022-03-18 00:00:00+03:00,34442.0,1051565.7,524.0
|
| 765 |
+
2022-03-19 00:00:00+03:00,32958.0,1052332.0,495.0
|
| 766 |
+
2022-03-20 00:00:00+03:00,31035.0,1008258.0,434.0
|
| 767 |
+
2022-03-21 00:00:00+03:00,28709.0,974620.0,409.0
|
| 768 |
+
2022-03-22 00:00:00+03:00,26394.0,925622.0,472.0
|
| 769 |
+
2022-03-23 00:00:00+03:00,26826.0,881397.0,429.0
|
| 770 |
+
2022-03-24 00:00:00+03:00,25387.0,839890.0,418.0
|
| 771 |
+
2022-03-25 00:00:00+03:00,25382.0,803014.0,398.0
|
| 772 |
+
2022-03-26 00:00:00+03:00,24072.0,765758.0,395.0
|
| 773 |
+
2022-03-27 00:00:00+03:00,23280.0,741160.0,338.0
|
| 774 |
+
2022-03-28 00:00:00+03:00,21101.0,726180.0,335.0
|
| 775 |
+
2022-03-29 00:00:00+03:00,19660.0,698272.0,339.0
|
| 776 |
+
2022-03-30 00:00:00+03:00,20145.0,666980.0,352.0
|
| 777 |
+
2022-03-31 00:00:00+03:00,19277.0,635416.0,345.0
|
| 778 |
+
2022-04-01 00:00:00+03:00,19164.0,608240.0,342.0
|
| 779 |
+
2022-04-02 00:00:00+03:00,17949.0,581302.0,340.0
|
| 780 |
+
2022-04-03 00:00:00+03:00,16828.0,562131.0,304.0
|
| 781 |
+
2022-04-04 00:00:00+03:00,15291.0,544285.0,287.0
|
| 782 |
+
2022-04-05 00:00:00+03:00,13947.0,520457.0,316.0
|
| 783 |
+
2022-04-06 00:00:00+03:00,14661.0,499521.0,291.0
|
| 784 |
+
2022-04-07 00:00:00+03:00,14355.0,478285.0,287.0
|
| 785 |
+
2022-04-08 00:00:00+03:00,14311.0,454632.0,280.0
|
| 786 |
+
2022-04-09 00:00:00+03:00,13573.0,427817.0,288.0
|
| 787 |
+
2022-04-10 00:00:00+03:00,13056.0,411242.0,259.0
|
| 788 |
+
2022-04-11 00:00:00+03:00,11855.0,396094.0,248.0
|
| 789 |
+
2022-04-12 00:00:00+03:00,10910.0,375452.0,281.0
|
| 790 |
+
2022-04-13 00:00:00+03:00,11754.0,358804.0,267.0
|
| 791 |
+
2022-04-14 00:00:00+03:00,11348.0,348045.0,254.0
|
| 792 |
+
2022-04-15 00:00:00+03:00,11432.0,339693.0,261.0
|
| 793 |
+
2022-04-16 00:00:00+03:00,11095.0,331542.0,240.0
|
| 794 |
+
2022-04-17 00:00:00+03:00,10263.0,325076.0,233.0
|
| 795 |
+
2022-04-18 00:00:00+03:00,9434.0,319973.0,213.0
|
| 796 |
+
2022-04-19 00:00:00+03:00,8640.0,311615.0,235.0
|
| 797 |
+
2022-04-20 00:00:00+03:00,9195.0,304387.0,223.0
|
| 798 |
+
2022-04-21 00:00:00+03:00,8875.0,298883.0,197.0
|
| 799 |
+
2022-04-22 00:00:00+03:00,9001.0,294209.0,195.0
|
| 800 |
+
2022-04-23 00:00:00+03:00,8829.0,289837.0,171.0
|
| 801 |
+
2022-04-24 00:00:00+03:00,8446.0,287607.0,168.0
|
| 802 |
+
2022-04-25 00:00:00+03:00,7651.0,286244.0,159.0
|
| 803 |
+
2022-04-26 00:00:00+03:00,7107.0,281276.0,176.0
|
| 804 |
+
2022-04-27 00:00:00+03:00,7705.0,277341.0,163.0
|
| 805 |
+
2022-04-28 00:00:00+03:00,7681.0,273793.0,166.0
|
| 806 |
+
2022-04-29 00:00:00+03:00,7710.0,270301.0,161.0
|
| 807 |
+
2022-04-30 00:00:00+03:00,7363.0,266485.0,157.0
|
| 808 |
+
2022-05-01 00:00:00+03:00,7047.0,264652.0,147.0
|
| 809 |
+
2022-05-02 00:00:00+03:00,6207.0,263221.0,136.0
|
| 810 |
+
2022-05-03 00:00:00+03:00,5466.0,261510.0,125.0
|
| 811 |
+
2022-05-04 00:00:00+03:00,5093.0,259810.0,129.0
|
| 812 |
+
2022-05-05 00:00:00+03:00,5011.0,255959.0,139.0
|
| 813 |
+
2022-05-06 00:00:00+03:00,5541.0,251956.0,136.0
|
| 814 |
+
2022-05-07 00:00:00+03:00,5500.0,248675.0,132.0
|
| 815 |
+
2022-05-08 00:00:00+03:00,5447.0,247322.0,118.0
|
| 816 |
+
2022-05-09 00:00:00+03:00,5030.0,246290.0,103.0
|
| 817 |
+
2022-05-10 00:00:00+03:00,4531.0,245706.0,101.0
|
| 818 |
+
2022-05-11 00:00:00+03:00,4102.0,244667.0,98.0
|
| 819 |
+
2022-05-12 00:00:00+03:00,4065.0,241691.0,111.0
|
| 820 |
+
2022-05-13 00:00:00+03:00,4896.0,239225.0,105.0
|
| 821 |
+
2022-05-14 00:00:00+03:00,5047.0,236787.0,107.0
|
lab3_report.html
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/lab3_pipeline.py
CHANGED
|
@@ -1,32 +1,27 @@
|
|
| 1 |
-
#
|
| 2 |
"""
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
- загрузка CSV/Parquet
|
| 6 |
-
- предобработка / ресемплинг
|
| 7 |
-
- Box-Cox / log (если возможно)
|
| 8 |
-
- тесты стационарности (ADF, KPSS)
|
| 9 |
-
- создание лагов / роллов
|
| 10 |
-
- простые модели (Naive, SeasonalNaive, SARIMAX, auto_arima если доступен)
|
| 11 |
-
- VAR если мультивариатные данные
|
| 12 |
-
- генерация простого HTML-отчёта
|
| 13 |
"""
|
| 14 |
|
| 15 |
-
import argparse
|
| 16 |
import os
|
| 17 |
import math
|
| 18 |
import time
|
| 19 |
-
from typing import
|
| 20 |
|
| 21 |
import numpy as np
|
| 22 |
import pandas as pd
|
| 23 |
import matplotlib.pyplot as plt
|
| 24 |
|
| 25 |
-
# statsmodels
|
| 26 |
try:
|
| 27 |
from statsmodels.tsa.stattools import adfuller, kpss
|
| 28 |
from statsmodels.tsa.seasonal import seasonal_decompose
|
|
|
|
| 29 |
from statsmodels.stats.diagnostic import acorr_ljungbox
|
|
|
|
|
|
|
|
|
|
| 30 |
STATSMODELS_AVAILABLE = True
|
| 31 |
except Exception as e:
|
| 32 |
STATSMODELS_AVAILABLE = False
|
|
@@ -35,30 +30,81 @@ except Exception as e:
|
|
| 35 |
# optional heavy deps
|
| 36 |
try:
|
| 37 |
import pmdarima as pm
|
|
|
|
| 38 |
PM_AVAILABLE = True
|
| 39 |
except Exception:
|
| 40 |
PM_AVAILABLE = False
|
| 41 |
|
| 42 |
try:
|
| 43 |
-
from
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
SCIPY_AVAILABLE = True
|
| 45 |
except Exception:
|
| 46 |
SCIPY_AVAILABLE = False
|
| 47 |
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
| 49 |
def mae(y_true, y_pred): return np.mean(np.abs(y_true - y_pred))
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
def mape(y_true, y_pred): return np.mean(np.abs((y_true - y_pred) / (y_true + 1e-9))) * 100.0
|
| 52 |
-
def smape(y_true, y_pred): return 100.0 * np.mean(2.0 * np.abs(y_pred - y_true) / (np.abs(y_true) + np.abs(y_pred) + 1e-9))
|
| 53 |
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
def load_data(path: str, timestamp_col: str = 'timestamp', tz: Optional[str] = None) -> pd.DataFrame:
|
| 56 |
if path.endswith('.parquet'):
|
| 57 |
df = pd.read_parquet(path)
|
| 58 |
else:
|
| 59 |
df = pd.read_csv(path)
|
| 60 |
if timestamp_col not in df.columns:
|
| 61 |
-
raise ValueError(f"timestamp column '{timestamp_col}' not found
|
| 62 |
df[timestamp_col] = pd.to_datetime(df[timestamp_col], errors='coerce')
|
| 63 |
if tz is not None:
|
| 64 |
try:
|
|
@@ -72,6 +118,7 @@ def load_data(path: str, timestamp_col: str = 'timestamp', tz: Optional[str] = N
|
|
| 72 |
df = df.set_index(timestamp_col)
|
| 73 |
return df
|
| 74 |
|
|
|
|
| 75 |
def resample_and_interpolate(df: pd.DataFrame, freq: str = 'D', method: str = 'linear') -> pd.DataFrame:
|
| 76 |
dfr = df.resample(freq).asfreq()
|
| 77 |
if method == 'linear':
|
|
@@ -81,192 +128,718 @@ def resample_and_interpolate(df: pd.DataFrame, freq: str = 'D', method: str = 'l
|
|
| 81 |
else:
|
| 82 |
return dfr.fillna(method='ffill').interpolate()
|
| 83 |
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
def apply_boxcox(series: pd.Series, lmbda: Optional[float] = None):
|
| 91 |
if not SCIPY_AVAILABLE:
|
| 92 |
-
raise ImportError("scipy required for boxcox
|
| 93 |
s = series.dropna()
|
| 94 |
if (s <= 0).any():
|
| 95 |
raise ValueError("Box-Cox requires positive values")
|
| 96 |
if lmbda is None:
|
| 97 |
-
lmbda = boxcox_normmax(s, brack=(-2,2))
|
| 98 |
transformed = boxcox(s, lmbda)
|
| 99 |
-
|
| 100 |
-
return out, lmbda
|
| 101 |
|
| 102 |
-
def difference(series: pd.Series, d=1):
|
| 103 |
-
return series.diff(d)
|
| 104 |
|
| 105 |
-
#
|
|
|
|
|
|
|
| 106 |
def make_lags(df: pd.DataFrame, col: str, lags: List[int]):
|
| 107 |
for l in lags:
|
| 108 |
df[f'{col}_lag_{l}'] = df[col].shift(l)
|
| 109 |
return df
|
| 110 |
|
|
|
|
| 111 |
def make_rolls(df: pd.DataFrame, col: str, windows: List[int]):
|
| 112 |
for w in windows:
|
| 113 |
df[f'{col}_roll_mean_{w}'] = df[col].rolling(window=w, min_periods=1).mean()
|
| 114 |
-
df[f'{col}_roll_std_{w}']
|
| 115 |
-
df[f'{col}_roll_min_{w}']
|
| 116 |
-
df[f'{col}_roll_max_{w}']
|
| 117 |
return df
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
|
|
|
| 121 |
df['dayofweek'] = idx.dayofweek
|
| 122 |
df['month'] = idx.month
|
|
|
|
| 123 |
df['sin_week'] = np.sin(2 * np.pi * df['dayofweek'] / 7)
|
| 124 |
df['cos_month'] = np.cos(2 * np.pi * (df['month'] - 1) / 12)
|
| 125 |
return df
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
def chronological_split(df: pd.DataFrame, frac_train=0.7, frac_val=0.15):
|
| 128 |
n = len(df)
|
| 129 |
i_train = int(n * frac_train)
|
| 130 |
i_val = i_train + int(n * frac_val)
|
|
|
|
| 131 |
train = df.iloc[:i_train].copy()
|
| 132 |
val = df.iloc[i_train:i_val].copy()
|
| 133 |
test = df.iloc[i_val:].copy()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
return train, val, test
|
| 135 |
|
| 136 |
-
# ---- simple models / wrappers ----
|
| 137 |
-
def naive_forecast(train: pd.Series, steps: int):
|
| 138 |
-
last = train.iloc[-1]
|
| 139 |
-
return np.full(steps, last)
|
| 140 |
|
| 141 |
-
def
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 146 |
|
| 147 |
-
def fit_sarimax_wrapper(series: pd.Series, order=(1,0,0), seasonal_order=(0,0,0,0), **kwargs):
|
| 148 |
-
if not STATSMODELS_AVAILABLE:
|
| 149 |
-
raise ImportError("statsmodels required for SARIMAX")
|
| 150 |
-
from statsmodels.tsa.statespace.sarimax import SARIMAX
|
| 151 |
-
m = SARIMAX(series.dropna(), order=order, seasonal_order=seasonal_order,
|
| 152 |
-
enforce_stationarity=False, enforce_invertibility=False)
|
| 153 |
-
res = m.fit(disp=False, **kwargs)
|
| 154 |
-
return res
|
| 155 |
|
| 156 |
-
|
|
|
|
|
|
|
| 157 |
if hasattr(fit_res, "get_forecast"):
|
| 158 |
fc = fit_res.get_forecast(steps=steps)
|
| 159 |
mean = np.asarray(fc.predicted_mean)
|
| 160 |
try:
|
| 161 |
-
conf = fc.conf_int(
|
| 162 |
-
|
| 163 |
-
|
| 164 |
except Exception:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
lower = np.full(len(mean), np.nan)
|
| 166 |
upper = np.full(len(mean), np.nan)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
return mean, (lower, upper)
|
| 168 |
-
else:
|
| 169 |
-
f = fit_res.forecast(steps=steps)
|
| 170 |
-
mean = np.asarray(f)
|
| 171 |
-
return mean, (np.full(len(mean), np.nan), np.full(len(mean), np.nan))
|
| 172 |
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
for name, df in tables.items():
|
| 177 |
html_parts.append(f"<h2>{name}</h2>")
|
| 178 |
-
html_parts.append(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
for i, fig in enumerate(plots):
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 183 |
html_parts.append("</body></html>")
|
|
|
|
| 184 |
with open(out_path, 'w', encoding='utf-8') as f:
|
| 185 |
f.write("\n".join(html_parts))
|
| 186 |
print("Report saved to", out_path)
|
| 187 |
|
| 188 |
-
#
|
| 189 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
df = load_data(data_path, timestamp_col)
|
| 191 |
if target_col not in df.columns:
|
| 192 |
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
| 193 |
if not numeric_cols:
|
| 194 |
-
raise ValueError("No numeric columns found
|
| 195 |
target_col = numeric_cols[0]
|
|
|
|
| 196 |
|
| 197 |
series = df[target_col].astype('float').copy()
|
| 198 |
-
series = resample_and_interpolate(series.to_frame(), freq=freq).iloc[:,0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
|
| 200 |
-
|
|
|
|
| 201 |
df_all = make_time_features(df_all)
|
| 202 |
-
df_all = make_lags(df_all, target_col, [1,2,7,30])
|
| 203 |
-
df_all = make_rolls(df_all, target_col, [7,30])
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
|
|
|
|
|
|
|
|
|
| 207 |
y_test = test[target_col]
|
|
|
|
| 208 |
|
| 209 |
-
|
| 210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
for h in horizons:
|
| 212 |
-
|
| 213 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
-
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
try:
|
| 218 |
-
|
|
|
|
|
|
|
| 219 |
for h in horizons:
|
| 220 |
-
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
except Exception as e:
|
| 223 |
-
print("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|
| 225 |
# pmdarima auto_arima
|
| 226 |
if PM_AVAILABLE:
|
| 227 |
try:
|
| 228 |
-
auto =
|
| 229 |
for h in horizons:
|
| 230 |
p = auto.predict(n_periods=h)
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
except Exception as e:
|
| 233 |
print("auto_arima failed:", e)
|
| 234 |
|
| 235 |
-
#
|
| 236 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
plots = []
|
|
|
|
| 238 |
for rec in results:
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
continue
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
'
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
ax.legend()
|
| 256 |
plots.append(fig)
|
| 257 |
|
| 258 |
-
eval_df = pd.DataFrame(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
tables = {'evaluation': eval_df}
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
| 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 |
+
|
| 71 |
+
# -------------------------------------------------------------------------
|
| 72 |
+
# Metrics
|
| 73 |
+
# -------------------------------------------------------------------------
|
| 74 |
def mae(y_true, y_pred): return np.mean(np.abs(y_true - y_pred))
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def rmse(y_true, y_pred): return math.sqrt(np.mean((y_true - y_pred) ** 2))
|
| 78 |
+
|
| 79 |
+
|
| 80 |
def mape(y_true, y_pred): return np.mean(np.abs((y_true - y_pred) / (y_true + 1e-9))) * 100.0
|
|
|
|
| 81 |
|
| 82 |
+
|
| 83 |
+
def smape(y_true, y_pred): return 100.0 * np.mean(
|
| 84 |
+
2.0 * np.abs(y_pred - y_true) / (np.abs(y_true) + np.abs(y_pred) + 1e-9))
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def rmsle(y_true, y_pred): return math.sqrt(np.mean((np.log1p(y_pred) - np.log1p(y_true)) ** 2))
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def mase(y_true, y_pred, naive_ref):
|
| 91 |
+
# naive_ref: series used to compute naive diff (e.g. train series)
|
| 92 |
+
denom = np.mean(np.abs(np.diff(naive_ref)))
|
| 93 |
+
if denom == 0:
|
| 94 |
+
return np.nan
|
| 95 |
+
return np.mean(np.abs(y_true - y_pred)) / denom
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
# -------------------------------------------------------------------------
|
| 99 |
+
# IO & preprocessing utilities
|
| 100 |
+
# -------------------------------------------------------------------------
|
| 101 |
def load_data(path: str, timestamp_col: str = 'timestamp', tz: Optional[str] = None) -> pd.DataFrame:
|
| 102 |
if path.endswith('.parquet'):
|
| 103 |
df = pd.read_parquet(path)
|
| 104 |
else:
|
| 105 |
df = pd.read_csv(path)
|
| 106 |
if timestamp_col not in df.columns:
|
| 107 |
+
raise ValueError(f"timestamp column '{timestamp_col}' not found")
|
| 108 |
df[timestamp_col] = pd.to_datetime(df[timestamp_col], errors='coerce')
|
| 109 |
if tz is not None:
|
| 110 |
try:
|
|
|
|
| 118 |
df = df.set_index(timestamp_col)
|
| 119 |
return df
|
| 120 |
|
| 121 |
+
|
| 122 |
def resample_and_interpolate(df: pd.DataFrame, freq: str = 'D', method: str = 'linear') -> pd.DataFrame:
|
| 123 |
dfr = df.resample(freq).asfreq()
|
| 124 |
if method == 'linear':
|
|
|
|
| 128 |
else:
|
| 129 |
return dfr.fillna(method='ffill').interpolate()
|
| 130 |
|
| 131 |
+
|
| 132 |
+
# -------------------------------------------------------------------------
|
| 133 |
+
# Transformations and stationarity selection
|
| 134 |
+
# -------------------------------------------------------------------------
|
| 135 |
+
def test_stationarity_pair(series: pd.Series) -> Dict[str, Dict[str, Any]]:
|
| 136 |
+
"""Возвращает результаты ADF и KPSS"""
|
| 137 |
+
res = {}
|
| 138 |
+
if not STATSMODELS_AVAILABLE:
|
| 139 |
+
raise ImportError("statsmodels required for stationarity tests")
|
| 140 |
+
s = series.dropna()
|
| 141 |
+
if len(s) < 3:
|
| 142 |
+
return {'adf': {'pvalue': np.nan}, 'kpss': {'pvalue': np.nan}}
|
| 143 |
+
adf_res = adfuller(s, autolag='AIC', regression='c')
|
| 144 |
+
kpss_res = kpss(s, nlags='auto')
|
| 145 |
+
return {'adf': {'stat': adf_res[0], 'pvalue': adf_res[1]}, 'kpss': {'stat': kpss_res[0], 'pvalue': kpss_res[1]}}
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def try_transformations_and_choose(y_train: pd.Series, seasonal_period: int = 7):
|
| 149 |
+
"""
|
| 150 |
+
Пробуем набор преобразований:
|
| 151 |
+
- none
|
| 152 |
+
- log (если >0)
|
| 153 |
+
- boxcox (если >0 и scipy доступен)
|
| 154 |
+
- diff(1), diff(s), diff(1).diff(s)
|
| 155 |
+
Выбираем ту комбинацию, которая минимизирует конфликт ADF/KPSS:
|
| 156 |
+
критерий: ADF.pvalue < 0.05 (хочется) и KPSS.pvalue > 0.05 (хочется).
|
| 157 |
+
Возвращаем: transformed_series, meta dict (applied transformations, lambda)
|
| 158 |
+
"""
|
| 159 |
+
candidates = []
|
| 160 |
+
# original
|
| 161 |
+
candidates.append(('none', y_train))
|
| 162 |
+
# log
|
| 163 |
+
if (y_train > 0).all():
|
| 164 |
+
candidates.append(('log', np.log(y_train)))
|
| 165 |
+
# boxcox
|
| 166 |
+
if (y_train > 0).all() and SCIPY_AVAILABLE:
|
| 167 |
+
try:
|
| 168 |
+
lam = boxcox_normmax(y_train.dropna(), brack=(-2, 2))
|
| 169 |
+
bc, _ = apply_boxcox(y_train, lmbda=lam)
|
| 170 |
+
candidates.append((f'boxcox_{lam:.4f}', bc))
|
| 171 |
+
except Exception:
|
| 172 |
+
pass
|
| 173 |
+
# differenced versions
|
| 174 |
+
# diff1 of original or of transformed series
|
| 175 |
+
final_candidates = []
|
| 176 |
+
for name, ser in candidates:
|
| 177 |
+
ser_clean = ser.dropna()
|
| 178 |
+
final_candidates.append((name, 0, ser_clean)) # 0 differences
|
| 179 |
+
if len(ser_clean) > 3:
|
| 180 |
+
final_candidates.append((name, 1, ser_clean.diff(1).dropna()))
|
| 181 |
+
if seasonal_period and len(ser_clean) > seasonal_period + 3:
|
| 182 |
+
final_candidates.append((name, seasonal_period, ser_clean.diff(seasonal_period).dropna()))
|
| 183 |
+
final_candidates.append(
|
| 184 |
+
(name, ('1+s', seasonal_period), ser_clean.diff(1).diff(seasonal_period).dropna()))
|
| 185 |
+
# Evaluate candidates
|
| 186 |
+
scored = []
|
| 187 |
+
for cand in final_candidates:
|
| 188 |
+
tag = cand[0]
|
| 189 |
+
d = cand[1]
|
| 190 |
+
ser = cand[2]
|
| 191 |
+
if ser.dropna().shape[0] < 10:
|
| 192 |
+
continue
|
| 193 |
+
try:
|
| 194 |
+
tests = test_stationarity_pair(ser)
|
| 195 |
+
# score: lower is better. We want ADF.p < 0.05 and KPSS.p > 0.05.
|
| 196 |
+
adf_p = tests['adf']['pvalue'] if tests['adf']['pvalue'] is not None else 1.0
|
| 197 |
+
kpss_p = tests['kpss']['pvalue'] if tests['kpss']['pvalue'] is not None else 0.0
|
| 198 |
+
# penalty for bad ADF (want small) and bad KPSS (want big)
|
| 199 |
+
score = (adf_p) + (1.0 - kpss_p)
|
| 200 |
+
scored.append({'tag': tag, 'diff': d, 'score': score, 'adf_p': adf_p, 'kpss_p': kpss_p, 'series': ser})
|
| 201 |
+
except Exception:
|
| 202 |
+
continue
|
| 203 |
+
if not scored:
|
| 204 |
+
return y_train, {'method': 'none', 'lambda': None}
|
| 205 |
+
scored = sorted(scored, key=lambda x: x['score'])
|
| 206 |
+
best = scored[0]
|
| 207 |
+
meta = {'method': best['tag'], 'diff': best['diff'], 'adf_p': best['adf_p'], 'kpss_p': best['kpss_p']}
|
| 208 |
+
return best['series'], meta
|
| 209 |
+
|
| 210 |
|
| 211 |
def apply_boxcox(series: pd.Series, lmbda: Optional[float] = None):
|
| 212 |
if not SCIPY_AVAILABLE:
|
| 213 |
+
raise ImportError("scipy required for boxcox")
|
| 214 |
s = series.dropna()
|
| 215 |
if (s <= 0).any():
|
| 216 |
raise ValueError("Box-Cox requires positive values")
|
| 217 |
if lmbda is None:
|
| 218 |
+
lmbda = boxcox_normmax(s, brack=(-2, 2))
|
| 219 |
transformed = boxcox(s, lmbda)
|
| 220 |
+
return pd.Series(index=s.index, data=transformed), float(lmbda)
|
|
|
|
| 221 |
|
|
|
|
|
|
|
| 222 |
|
| 223 |
+
# -------------------------------------------------------------------------
|
| 224 |
+
# Feature engineering
|
| 225 |
+
# -------------------------------------------------------------------------
|
| 226 |
def make_lags(df: pd.DataFrame, col: str, lags: List[int]):
|
| 227 |
for l in lags:
|
| 228 |
df[f'{col}_lag_{l}'] = df[col].shift(l)
|
| 229 |
return df
|
| 230 |
|
| 231 |
+
|
| 232 |
def make_rolls(df: pd.DataFrame, col: str, windows: List[int]):
|
| 233 |
for w in windows:
|
| 234 |
df[f'{col}_roll_mean_{w}'] = df[col].rolling(window=w, min_periods=1).mean()
|
| 235 |
+
df[f'{col}_roll_std_{w}'] = df[col].rolling(window=w, min_periods=1).std()
|
| 236 |
+
df[f'{col}_roll_min_{w}'] = df[col].rolling(window=w, min_periods=1).min()
|
| 237 |
+
df[f'{col}_roll_max_{w}'] = df[col].rolling(window=w, min_periods=1).max()
|
| 238 |
return df
|
| 239 |
|
| 240 |
+
|
| 241 |
+
def make_time_features(df: pd.DataFrame):
|
| 242 |
+
idx = df.index
|
| 243 |
df['dayofweek'] = idx.dayofweek
|
| 244 |
df['month'] = idx.month
|
| 245 |
+
df['is_weekend'] = idx.dayofweek >= 5
|
| 246 |
df['sin_week'] = np.sin(2 * np.pi * df['dayofweek'] / 7)
|
| 247 |
df['cos_month'] = np.cos(2 * np.pi * (df['month'] - 1) / 12)
|
| 248 |
return df
|
| 249 |
|
| 250 |
+
|
| 251 |
+
# -------------------------------------------------------------------------
|
| 252 |
+
# Splits, CV and strategies
|
| 253 |
+
# -------------------------------------------------------------------------
|
| 254 |
def chronological_split(df: pd.DataFrame, frac_train=0.7, frac_val=0.15):
|
| 255 |
n = len(df)
|
| 256 |
i_train = int(n * frac_train)
|
| 257 |
i_val = i_train + int(n * frac_val)
|
| 258 |
+
|
| 259 |
train = df.iloc[:i_train].copy()
|
| 260 |
val = df.iloc[i_train:i_val].copy()
|
| 261 |
test = df.iloc[i_val:].copy()
|
| 262 |
+
|
| 263 |
+
# Проверяем непрерывность дат
|
| 264 |
+
all_data = pd.concat([train, val, test])
|
| 265 |
+
date_diff = (all_data.index[1:] - all_data.index[:-1]).value_counts()
|
| 266 |
+
|
| 267 |
+
if len(date_diff) > 1:
|
| 268 |
+
print(f"Предупреждение: обнаружены разные интервалы между датами: {date_diff.index.tolist()}")
|
| 269 |
+
|
| 270 |
return train, val, test
|
| 271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
|
| 273 |
+
def expanding_window_cv(X: pd.DataFrame, y: pd.Series, model_fit_predict, initial_train_size: int, h: int,
|
| 274 |
+
n_splits: int = 5):
|
| 275 |
+
"""Expanding window: [0:t] -> [t+1:t+h]"""
|
| 276 |
+
n = len(X)
|
| 277 |
+
step = (n - initial_train_size - h) // n_splits if n_splits > 0 else h
|
| 278 |
+
metrics = []
|
| 279 |
+
for i in range(n_splits):
|
| 280 |
+
end_train = initial_train_size + i * step
|
| 281 |
+
train_X, train_y = X.iloc[:end_train], y.iloc[:end_train]
|
| 282 |
+
test_X, test_y = X.iloc[end_train:end_train + h], y.iloc[end_train:end_train + h]
|
| 283 |
+
y_pred = model_fit_predict(train_X, train_y, h)
|
| 284 |
+
metrics.append({'fold': i, 'mae': mae(test_y.values, y_pred), 'rmse': rmse(test_y.values, y_pred)})
|
| 285 |
+
return pd.DataFrame(metrics)
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
def rolling_window_cv(X: pd.DataFrame, y: pd.Series, model_fit_predict, window: int, h: int, n_splits: int = 5):
|
| 289 |
+
"""Rolling window: [t-w:t] -> [t+1:t+h]"""
|
| 290 |
+
n = len(X)
|
| 291 |
+
step = (n - window - h) // n_splits if n_splits > 0 else h
|
| 292 |
+
metrics = []
|
| 293 |
+
for i in range(n_splits):
|
| 294 |
+
start = i * step
|
| 295 |
+
end = start + window
|
| 296 |
+
train_X, train_y = X.iloc[start:end], y.iloc[start:end]
|
| 297 |
+
test_X, test_y = X.iloc[end:end + h], y.iloc[end:end + h]
|
| 298 |
+
y_pred = model_fit_predict(train_X, train_y, h)
|
| 299 |
+
metrics.append({'fold': i, 'mae': mae(test_y.values, y_pred), 'rmse': rmse(test_y.values, y_pred)})
|
| 300 |
+
return pd.DataFrame(metrics)
|
| 301 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
+
# Strategies: recursive, direct, hybrid
|
| 304 |
+
def forecast_recursive_arima(fit_res, steps: int, last_date: pd.Timestamp = None, freq: str = 'D'):
|
| 305 |
+
"""Wrapper for SARIMAX results with proper date index"""
|
| 306 |
if hasattr(fit_res, "get_forecast"):
|
| 307 |
fc = fit_res.get_forecast(steps=steps)
|
| 308 |
mean = np.asarray(fc.predicted_mean)
|
| 309 |
try:
|
| 310 |
+
conf = fc.conf_int()
|
| 311 |
+
low = np.asarray(conf.iloc[:, 0])
|
| 312 |
+
high = np.asarray(conf.iloc[:, 1])
|
| 313 |
except Exception:
|
| 314 |
+
low = np.full(len(mean), np.nan)
|
| 315 |
+
high = np.full(len(mean), np.nan)
|
| 316 |
+
|
| 317 |
+
# Создаем правильный индекс
|
| 318 |
+
if last_date is not None:
|
| 319 |
+
dates = create_forecast_index(last_date, steps, freq)
|
| 320 |
+
mean = pd.Series(mean, index=dates)
|
| 321 |
+
low = pd.Series(low, index=dates)
|
| 322 |
+
high = pd.Series(high, index=dates)
|
| 323 |
+
|
| 324 |
+
return mean, (low, high)
|
| 325 |
+
else:
|
| 326 |
+
mean = fit_res.forecast(steps=steps)
|
| 327 |
+
if last_date is not None:
|
| 328 |
+
dates = create_forecast_index(last_date, steps, freq)
|
| 329 |
+
mean = pd.Series(mean, index=dates)
|
| 330 |
+
return mean, (None, None)
|
| 331 |
+
|
| 332 |
+
|
| 333 |
+
# Direct strategy for SARIMAX: fit separate models for each horizon
|
| 334 |
+
def forecast_direct_arima(train_series: pd.Series, h: int, order=(1, 0, 0)):
|
| 335 |
+
if not STATSMODELS_AVAILABLE:
|
| 336 |
+
raise ImportError("statsmodels required")
|
| 337 |
+
# create shifted target for forecasting h steps ahead
|
| 338 |
+
df = train_series.to_frame("y")
|
| 339 |
+
df['y_target_h'] = df['y'].shift(-h)
|
| 340 |
+
df = df.dropna()
|
| 341 |
+
# naive approach: use previous value as predictor (simple)
|
| 342 |
+
last = train_series.iloc[-1]
|
| 343 |
+
return np.full(h, last)
|
| 344 |
+
|
| 345 |
+
|
| 346 |
+
# -------------------------------------------------------------------------
|
| 347 |
+
# Models training wrapper
|
| 348 |
+
# -------------------------------------------------------------------------
|
| 349 |
+
def fit_sarimax_simple(series: pd.Series, order=(1, 0, 0), seasonal_order=(0, 0, 0, 0), **kwargs):
|
| 350 |
+
if not STATSMODELS_AVAILABLE:
|
| 351 |
+
raise ImportError("statsmodels required")
|
| 352 |
+
m = SARIMAX(series.dropna(), order=order, seasonal_order=seasonal_order,
|
| 353 |
+
enforce_stationarity=False, enforce_invertibility=False)
|
| 354 |
+
res = m.fit(disp=False, **kwargs)
|
| 355 |
+
return res
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def forecast_sarimax(fit_res, steps: int, alpha: float = 0.05) -> Tuple[np.ndarray, Tuple[np.ndarray, np.ndarray]]:
|
| 359 |
+
"""
|
| 360 |
+
Делает прогноз из обученного SARIMAX-результата.
|
| 361 |
+
Возвращает (mean, (lower, upper)) — numpy arrays длины steps.
|
| 362 |
+
"""
|
| 363 |
+
try:
|
| 364 |
+
if hasattr(fit_res, "get_forecast"):
|
| 365 |
+
fc = fit_res.get_forecast(steps=steps)
|
| 366 |
+
mean = np.asarray(fc.predicted_mean)
|
| 367 |
+
|
| 368 |
+
# Проверяем на NaN
|
| 369 |
+
if np.any(np.isnan(mean)):
|
| 370 |
+
# Fallback: используем простой forecast
|
| 371 |
+
mean = fit_res.forecast(steps=steps)
|
| 372 |
+
mean = np.asarray(mean)
|
| 373 |
+
|
| 374 |
+
try:
|
| 375 |
+
conf = fc.conf_int(alpha=alpha)
|
| 376 |
+
lower = np.asarray(conf.iloc[:, 0])
|
| 377 |
+
upper = np.asarray(conf.iloc[:, 1])
|
| 378 |
+
|
| 379 |
+
# Проверяем доверительные интервалы на NaN
|
| 380 |
+
if np.any(np.isnan(lower)) or np.any(np.isnan(upper)):
|
| 381 |
+
lower = np.full(len(mean), np.nan)
|
| 382 |
+
upper = np.full(len(mean), np.nan)
|
| 383 |
+
|
| 384 |
+
except Exception:
|
| 385 |
+
lower = np.full(len(mean), np.nan)
|
| 386 |
+
upper = np.full(len(mean), np.nan)
|
| 387 |
+
|
| 388 |
+
return mean, (lower, upper)
|
| 389 |
+
else:
|
| 390 |
+
# fallback на forecast
|
| 391 |
+
mean = fit_res.forecast(steps=steps)
|
| 392 |
+
mean = np.asarray(mean)
|
| 393 |
lower = np.full(len(mean), np.nan)
|
| 394 |
upper = np.full(len(mean), np.nan)
|
| 395 |
+
return mean, (lower, upper)
|
| 396 |
+
|
| 397 |
+
except Exception as e:
|
| 398 |
+
# Если все методы не сработали, возвращаем массив NaN
|
| 399 |
+
print(f"Warning: SARIMAX forecast failed: {e}")
|
| 400 |
+
mean = np.full(steps, np.nan)
|
| 401 |
+
lower = np.full(steps, np.nan)
|
| 402 |
+
upper = np.full(steps, np.nan)
|
| 403 |
return mean, (lower, upper)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 404 |
|
| 405 |
+
def fit_auto_arima(series: pd.Series, seasonal=False, m=1, **kwargs):
|
| 406 |
+
if not PM_AVAILABLE:
|
| 407 |
+
raise ImportError("pmdarima not installed")
|
| 408 |
+
model = pm.auto_arima(series.dropna(), seasonal=seasonal, m=m, error_action='ignore', suppress_warnings=True,
|
| 409 |
+
**kwargs)
|
| 410 |
+
return model
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
def fit_var(df: pd.DataFrame, maxlags=15):
|
| 414 |
+
if not STATSMODELS_AVAILABLE:
|
| 415 |
+
raise ImportError("statsmodels required")
|
| 416 |
+
model = VAR(df.dropna())
|
| 417 |
+
sel = model.select_order(maxlags=maxlags)
|
| 418 |
+
best = 1
|
| 419 |
+
try:
|
| 420 |
+
so = sel.selected_orders
|
| 421 |
+
for k in ('aic', 'bic', 'fpe', 'hqic'):
|
| 422 |
+
if so.get(k) is not None:
|
| 423 |
+
best = int(so[k])
|
| 424 |
+
break
|
| 425 |
+
except Exception:
|
| 426 |
+
best = 1
|
| 427 |
+
res = model.fit(maxlags=best)
|
| 428 |
+
return res
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
def fit_garch_on_residuals(residuals, p=1, q=1):
|
| 432 |
+
if not ARCH_AVAILABLE:
|
| 433 |
+
raise ImportError("arch not installed")
|
| 434 |
+
am = arch_model(residuals, vol='Garch', p=p, q=q, dist='normal')
|
| 435 |
+
r = am.fit(disp='off')
|
| 436 |
+
return r
|
| 437 |
+
|
| 438 |
+
|
| 439 |
+
# -------------------------------------------------------------------------
|
| 440 |
+
# Diagnostics and tests
|
| 441 |
+
# -------------------------------------------------------------------------
|
| 442 |
+
def ljung_box_test(resid: np.ndarray, lags: List[int] = [10]):
|
| 443 |
+
if not STATSMODELS_AVAILABLE:
|
| 444 |
+
raise ImportError("statsmodels required")
|
| 445 |
+
res = acorr_ljungbox(resid, lags=lags, return_df=True)
|
| 446 |
+
return res
|
| 447 |
+
|
| 448 |
+
|
| 449 |
+
def shapiro_test(resid: np.ndarray):
|
| 450 |
+
if not SCIPY_AVAILABLE:
|
| 451 |
+
raise ImportError("scipy required")
|
| 452 |
+
stat, p = shapiro(resid)
|
| 453 |
+
return {'stat': stat, 'pvalue': p}
|
| 454 |
+
|
| 455 |
+
|
| 456 |
+
def simple_dm_test(e1: np.ndarray, e2: np.ndarray):
|
| 457 |
+
"""
|
| 458 |
+
Простая реализация Diebold-Mariano теста по разности квадратических ошибок.
|
| 459 |
+
Возвращает t-stat и p-value (двухсторонний).
|
| 460 |
+
Примечание: это упрощённая версия, без HAC коррекции.
|
| 461 |
+
"""
|
| 462 |
+
# use squared error loss
|
| 463 |
+
d = (e1 - e2)
|
| 464 |
+
n = len(d)
|
| 465 |
+
dbar = np.mean(d)
|
| 466 |
+
sd = np.var(d, ddof=1)
|
| 467 |
+
denom = math.sqrt(sd / n) if sd > 0 else np.nan
|
| 468 |
+
if denom == 0 or np.isnan(denom):
|
| 469 |
+
return {'stat': np.nan, 'pvalue': np.nan}
|
| 470 |
+
tstat = dbar / denom
|
| 471 |
+
# two-sided pval from Student's t approx
|
| 472 |
+
from scipy.stats import t as student_t
|
| 473 |
+
pval = 2 * (1 - student_t.cdf(abs(tstat), df=n - 1))
|
| 474 |
+
return {'stat': float(tstat), 'pvalue': float(pval)}
|
| 475 |
+
|
| 476 |
+
|
| 477 |
+
# -------------------------------------------------------------------------
|
| 478 |
+
# Report generation (HTML)
|
| 479 |
+
# -------------------------------------------------------------------------
|
| 480 |
+
def generate_report_html(out_path: str, plots: List[plt.Figure], tables: Dict[str, pd.DataFrame], title="Lab3 Report"):
|
| 481 |
+
import base64
|
| 482 |
+
from io import BytesIO
|
| 483 |
+
|
| 484 |
+
html_parts = [f"""
|
| 485 |
+
<html>
|
| 486 |
+
<head>
|
| 487 |
+
<meta charset='utf-8'>
|
| 488 |
+
<title>{title}</title>
|
| 489 |
+
<style>
|
| 490 |
+
body {{ font-family: Arial, sans-serif; margin: 20px; background-color: white; color: black; }}
|
| 491 |
+
table {{ border-collapse: collapse; width: 100%; margin: 10px 0; }}
|
| 492 |
+
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
|
| 493 |
+
th {{ background-color: #f2f2f2; }}
|
| 494 |
+
img {{ max-width: 100%; height: auto; margin: 10px 0; }}
|
| 495 |
+
.table-container {{ overflow-x: auto; }}
|
| 496 |
+
</style>
|
| 497 |
+
</head>
|
| 498 |
+
<body>
|
| 499 |
+
<h1>{title}</h1>
|
| 500 |
+
"""]
|
| 501 |
+
|
| 502 |
+
# Таблицы
|
| 503 |
for name, df in tables.items():
|
| 504 |
html_parts.append(f"<h2>{name}</h2>")
|
| 505 |
+
html_parts.append('<div class="table-container">')
|
| 506 |
+
html_parts.append(df.to_html(classes='table table-striped', border=0, index=True))
|
| 507 |
+
html_parts.append('</div>')
|
| 508 |
+
|
| 509 |
+
# Графики как base64
|
| 510 |
for i, fig in enumerate(plots):
|
| 511 |
+
# Сохраняем рисунок в буфер
|
| 512 |
+
buf = BytesIO()
|
| 513 |
+
fig.savefig(buf, format='png', bbox_inches='tight', dpi=100)
|
| 514 |
+
buf.seek(0)
|
| 515 |
+
|
| 516 |
+
# Кодируем в base64
|
| 517 |
+
img_data = base64.b64encode(buf.read()).decode('utf-8')
|
| 518 |
+
html_parts.append(f'<h3>Figure {i + 1}</h3>')
|
| 519 |
+
html_parts.append(f'<img src="data:image/png;base64,{img_data}" alt="Figure {i + 1}">')
|
| 520 |
+
|
| 521 |
+
# Закрываем рисунок чтобы освободить память
|
| 522 |
+
plt.close(fig)
|
| 523 |
+
|
| 524 |
html_parts.append("</body></html>")
|
| 525 |
+
|
| 526 |
with open(out_path, 'w', encoding='utf-8') as f:
|
| 527 |
f.write("\n".join(html_parts))
|
| 528 |
print("Report saved to", out_path)
|
| 529 |
|
| 530 |
+
# -------------------------------------------------------------------------
|
| 531 |
+
# Main runner that orchestrates everything
|
| 532 |
+
# -------------------------------------------------------------------------
|
| 533 |
+
def run_pipeline(data_path: str, timestamp_col: str, target_col: str,
|
| 534 |
+
out_report: str = 'lab3_report.html', freq: str = 'D'):
|
| 535 |
+
"""
|
| 536 |
+
Главная точка запуска pipeline.
|
| 537 |
+
"""
|
| 538 |
+
print("Loading", data_path)
|
| 539 |
df = load_data(data_path, timestamp_col)
|
| 540 |
if target_col not in df.columns:
|
| 541 |
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
| 542 |
if not numeric_cols:
|
| 543 |
+
raise ValueError("No numeric columns found")
|
| 544 |
target_col = numeric_cols[0]
|
| 545 |
+
print("Target not found, using", target_col)
|
| 546 |
|
| 547 |
series = df[target_col].astype('float').copy()
|
| 548 |
+
series = resample_and_interpolate(series.to_frame(), freq=freq).iloc[:, 0]
|
| 549 |
+
print("Series length after resample:", len(series))
|
| 550 |
+
|
| 551 |
+
# 3.1 Preprocessing & transformation selection
|
| 552 |
+
transformed, meta = try_transformations_and_choose(series, seasonal_period=7)
|
| 553 |
+
print("Chosen transform:", meta)
|
| 554 |
|
| 555 |
+
# 3.2 Feature engineering
|
| 556 |
+
df_all = transformed.to_frame(name=target_col)
|
| 557 |
df_all = make_time_features(df_all)
|
| 558 |
+
df_all = make_lags(df_all, target_col, [1, 2, 7, 30])
|
| 559 |
+
df_all = make_rolls(df_all, target_col, [7, 30])
|
| 560 |
+
|
| 561 |
+
# dropna rows with lag features
|
| 562 |
+
df_all = df_all.dropna()
|
| 563 |
+
train, val, test = chronological_split(df_all, frac_train=0.7, frac_val=0.15)
|
| 564 |
+
y_train = train[target_col];
|
| 565 |
+
y_val = val[target_col];
|
| 566 |
y_test = test[target_col]
|
| 567 |
+
print("Sizes train/val/test:", len(y_train), len(y_val), len(y_test))
|
| 568 |
|
| 569 |
+
# 3.3 Models: benchmarks + SARIMAX + optional auto_arima + VAR
|
| 570 |
+
results = [] # each elem: dict(model, h, preds (np.array), extra)
|
| 571 |
+
horizons = [1, 7, 30]
|
| 572 |
+
|
| 573 |
+
# Определяем частоту для прогнозов
|
| 574 |
+
try:
|
| 575 |
+
inferred_freq = pd.infer_freq(y_train.index) or freq
|
| 576 |
+
except:
|
| 577 |
+
inferred_freq = freq
|
| 578 |
+
|
| 579 |
+
# Benchmarks
|
| 580 |
for h in horizons:
|
| 581 |
+
pred_values = np.full(h, y_train.iloc[-1])
|
| 582 |
+
pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
|
| 583 |
+
results.append({
|
| 584 |
+
'model': 'naive',
|
| 585 |
+
'h': h,
|
| 586 |
+
'pred': pd.Series(pred_values, index=pred_dates)
|
| 587 |
+
})
|
| 588 |
|
| 589 |
+
if len(y_train) >= 7:
|
| 590 |
+
seasonal_pred = seasonal_naive_forecast(y_train, season=7, steps=h)
|
| 591 |
+
seasonal_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
|
| 592 |
+
results.append({
|
| 593 |
+
'model': 'seasonal_naive',
|
| 594 |
+
'h': h,
|
| 595 |
+
'pred': pd.Series(seasonal_pred, index=seasonal_dates)
|
| 596 |
+
})
|
| 597 |
+
|
| 598 |
+
# SES/Holt (simple forecasting for 1-step and iterated for multi-step)
|
| 599 |
try:
|
| 600 |
+
from statsmodels.tsa.holtwinters import SimpleExpSmoothing, ExponentialSmoothing
|
| 601 |
+
# SES as simple baseline
|
| 602 |
+
ses = SimpleExpSmoothing(y_train.dropna()).fit(optimized=True)
|
| 603 |
for h in horizons:
|
| 604 |
+
pred = ses.forecast(h)
|
| 605 |
+
pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
|
| 606 |
+
results.append({
|
| 607 |
+
'model': 'SES',
|
| 608 |
+
'h': h,
|
| 609 |
+
'pred': pd.Series(pred, index=pred_dates)
|
| 610 |
+
})
|
| 611 |
except Exception as e:
|
| 612 |
+
print("SES skipped:", e)
|
| 613 |
+
|
| 614 |
+
# SARIMAX baseline
|
| 615 |
+
if STATSMODELS_AVAILABLE:
|
| 616 |
+
try:
|
| 617 |
+
# Проверяем, что данные подходят для SARIMAX
|
| 618 |
+
if len(y_train.dropna()) > 10 and y_train.var() > 1e-6: # достаточное количество точек и дисперсия
|
| 619 |
+
sar = fit_sarimax_simple(y_train, order=(1, 1, 1))
|
| 620 |
+
|
| 621 |
+
# Проверяем, что модель сходилась
|
| 622 |
+
if hasattr(sar, 'mle_retvals') and sar.mle_retvals.get('converged', False):
|
| 623 |
+
for h in horizons:
|
| 624 |
+
mean, (lower, upper) = forecast_sarimax(sar, steps=h)
|
| 625 |
+
|
| 626 |
+
# Проверяем, что прогнозы не все NaN
|
| 627 |
+
if not np.all(np.isnan(mean)):
|
| 628 |
+
pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
|
| 629 |
+
results.append({
|
| 630 |
+
'model': 'SARIMAX(1,1,1)',
|
| 631 |
+
'h': h,
|
| 632 |
+
'pred': pd.Series(mean, index=pred_dates)
|
| 633 |
+
})
|
| 634 |
+
else:
|
| 635 |
+
print(f"SARIMAX returned all NaN for horizon {h}")
|
| 636 |
+
else:
|
| 637 |
+
print("SARIMAX model did not converge")
|
| 638 |
+
else:
|
| 639 |
+
print("Insufficient data for SARIMAX")
|
| 640 |
+
except Exception as e:
|
| 641 |
+
print("SARIMAX failed:", e)
|
| 642 |
|
| 643 |
# pmdarima auto_arima
|
| 644 |
if PM_AVAILABLE:
|
| 645 |
try:
|
| 646 |
+
auto = fit_auto_arima(y_train, seasonal=False)
|
| 647 |
for h in horizons:
|
| 648 |
p = auto.predict(n_periods=h)
|
| 649 |
+
pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
|
| 650 |
+
results.append({
|
| 651 |
+
'model': 'auto_arima',
|
| 652 |
+
'h': h,
|
| 653 |
+
'pred': pd.Series(p, index=pred_dates)
|
| 654 |
+
})
|
| 655 |
except Exception as e:
|
| 656 |
print("auto_arima failed:", e)
|
| 657 |
|
| 658 |
+
# VAR if multivariate
|
| 659 |
+
if STATSMODELS_AVAILABLE and df.select_dtypes(include=[np.number]).shape[1] >= 2:
|
| 660 |
+
try:
|
| 661 |
+
num_df = df.select_dtypes(include=[np.number]).dropna()
|
| 662 |
+
var_res = fit_var(num_df, maxlags=5)
|
| 663 |
+
fut = var_res.forecast(var_res.endog[-var_res.k_ar:], steps=30)
|
| 664 |
+
# fut is array shape (30, k)
|
| 665 |
+
# wrap as predictions per horizon for the first variable
|
| 666 |
+
for h in [30]:
|
| 667 |
+
pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
|
| 668 |
+
results.append({
|
| 669 |
+
'model': 'VAR',
|
| 670 |
+
'h': h,
|
| 671 |
+
'pred': pd.Series(fut[:h, 0], index=pred_dates)
|
| 672 |
+
})
|
| 673 |
+
except Exception as e:
|
| 674 |
+
print("VAR failed:", e)
|
| 675 |
+
|
| 676 |
+
# 3.6 Diagnostics later for top models
|
| 677 |
+
# 3.7 Evaluate on test set
|
| 678 |
+
eval_rows = []
|
| 679 |
plots = []
|
| 680 |
+
|
| 681 |
for rec in results:
|
| 682 |
+
model_name = rec['model']
|
| 683 |
+
h = rec['h']
|
| 684 |
+
pred = rec['pred']
|
| 685 |
+
|
| 686 |
+
# Выравниваем прогнозы с тестовыми данными по времени
|
| 687 |
+
if hasattr(pred, 'index'):
|
| 688 |
+
# Для прогнозов с правильным индексом
|
| 689 |
+
aligned_pred = pred
|
| 690 |
+
# Берем только первые h точек тестовых данных для сравнения
|
| 691 |
+
y_true_aligned = y_test.iloc[:min(h, len(y_test))]
|
| 692 |
+
else:
|
| 693 |
+
# Для прогнозов без индекса (старый формат)
|
| 694 |
+
pred_values = np.asarray(pred).ravel()
|
| 695 |
+
aligned_pred = pd.Series(pred_values, index=y_test.index[:len(pred_values)])
|
| 696 |
+
y_true_aligned = y_test.iloc[:len(pred_values)]
|
| 697 |
+
|
| 698 |
+
if len(y_true_aligned) == 0:
|
| 699 |
continue
|
| 700 |
+
|
| 701 |
+
# Обрезаем прогноз до длины тестовых данных
|
| 702 |
+
aligned_pred = aligned_pred.iloc[:len(y_true_aligned)]
|
| 703 |
+
|
| 704 |
+
# Вычисляем метрики
|
| 705 |
+
row = {
|
| 706 |
+
'model': model_name,
|
| 707 |
+
'h': h,
|
| 708 |
+
'MAE': mae(y_true_aligned.values, aligned_pred.values),
|
| 709 |
+
'RMSE': rmse(y_true_aligned.values, aligned_pred.values),
|
| 710 |
+
'MAPE': mape(y_true_aligned.values, aligned_pred.values),
|
| 711 |
+
'SMAPE': smape(y_true_aligned.values, aligned_pred.values)
|
| 712 |
+
}
|
| 713 |
+
# MASE: use naive in-sample reference
|
| 714 |
+
row['MASE'] = mase(y_true_aligned.values, aligned_pred.values, y_train.values)
|
| 715 |
+
# R2 where possible
|
| 716 |
+
try:
|
| 717 |
+
row['R2'] = float((1 - np.sum((y_true_aligned.values - aligned_pred.values) ** 2) / np.sum(
|
| 718 |
+
(y_true_aligned.values - np.mean(y_true_aligned.values)) ** 2)))
|
| 719 |
+
except Exception:
|
| 720 |
+
row['R2'] = np.nan
|
| 721 |
+
eval_rows.append(row)
|
| 722 |
+
|
| 723 |
+
# Визуализация
|
| 724 |
+
fig, ax = plt.subplots(figsize=(8, 3))
|
| 725 |
+
|
| 726 |
+
# Показываем больше данных для контекста
|
| 727 |
+
context_points = min(200, len(y_train))
|
| 728 |
+
ax.plot(y_train.index[-context_points:], y_train.values[-context_points:],
|
| 729 |
+
label='train', alpha=0.7)
|
| 730 |
+
|
| 731 |
+
if len(val) > 0:
|
| 732 |
+
ax.plot(val.index, val.values, label='val', alpha=0.7)
|
| 733 |
+
|
| 734 |
+
ax.plot(y_test.index, y_test.values, label='test', alpha=0.7)
|
| 735 |
+
|
| 736 |
+
# Прогнозы с правильными датами
|
| 737 |
+
ax.plot(aligned_pred.index, aligned_pred.values,
|
| 738 |
+
label=f'pred_{model_name}_h{h}', linewidth=2)
|
| 739 |
+
|
| 740 |
ax.legend()
|
| 741 |
plots.append(fig)
|
| 742 |
|
| 743 |
+
eval_df = pd.DataFrame(eval_rows)
|
| 744 |
+
|
| 745 |
+
# Diagnostics for top-3 by RMSE
|
| 746 |
+
diag_tables = {}
|
| 747 |
+
try:
|
| 748 |
+
top3 = eval_df.sort_values('RMSE').head(3)['model'].tolist()
|
| 749 |
+
except Exception:
|
| 750 |
+
top3 = []
|
| 751 |
+
for m in top3:
|
| 752 |
+
# find corresponding fitted residuals if model was SARIMAX etc.
|
| 753 |
+
if m.startswith('SARIMAX'):
|
| 754 |
+
try:
|
| 755 |
+
resid = sar.resid.dropna()
|
| 756 |
+
lb = acorr_ljungbox(resid, lags=[10], return_df=True)
|
| 757 |
+
diag_tables[f'ljungbox_{m}'] = lb
|
| 758 |
+
if SCIPY_AVAILABLE:
|
| 759 |
+
sh = shapiro(resid)
|
| 760 |
+
diag_tables[f'shapiro_{m}'] = pd.DataFrame([{'stat': sh[0], 'pvalue': sh[1]}])
|
| 761 |
+
except Exception:
|
| 762 |
+
pass
|
| 763 |
+
|
| 764 |
+
# Diebold-Mariano pairwise for top 2 models (if available)
|
| 765 |
+
dm_table = None
|
| 766 |
+
try:
|
| 767 |
+
if len(eval_df) >= 2:
|
| 768 |
+
sorted_models = eval_df.sort_values('RMSE')
|
| 769 |
+
if len(sorted_models) >= 2:
|
| 770 |
+
m1 = sorted_models.iloc[0]['model']
|
| 771 |
+
m2 = sorted_models.iloc[1]['model']
|
| 772 |
+
# pick their predictions at h=1 (if exist)
|
| 773 |
+
pred1 = None;
|
| 774 |
+
pred2 = None
|
| 775 |
+
for rec in results:
|
| 776 |
+
if rec['model'] == m1 and rec['h'] == 1:
|
| 777 |
+
pred1 = rec['pred']
|
| 778 |
+
if rec['model'] == m2 and rec['h'] == 1:
|
| 779 |
+
pred2 = rec['pred']
|
| 780 |
+
if pred1 is not None and pred2 is not None:
|
| 781 |
+
# align lengths with test
|
| 782 |
+
y_true = y_test.values[:min(len(pred1), len(y_test))]
|
| 783 |
+
e1 = (y_true - pred1.values[:len(y_true)]) ** 2
|
| 784 |
+
e2 = (y_true - pred2.values[:len(y_true)]) ** 2
|
| 785 |
+
dm = simple_dm_test(e1, e2)
|
| 786 |
+
dm_table = pd.DataFrame(
|
| 787 |
+
[{'model1': m1, 'model2': m2, 'dm_stat': dm['stat'], 'pvalue': dm['pvalue']}])
|
| 788 |
+
except Exception:
|
| 789 |
+
dm_table = None
|
| 790 |
+
|
| 791 |
+
# Generate report
|
| 792 |
tables = {'evaluation': eval_df}
|
| 793 |
+
if diag_tables:
|
| 794 |
+
tables.update(diag_tables)
|
| 795 |
+
if dm_table is not None:
|
| 796 |
+
tables['dm_test'] = dm_table
|
| 797 |
+
|
| 798 |
+
generate_report_html(out_report, plots, tables, title="Lab3 Full Report")
|
| 799 |
+
print("Pipeline finished. Report:", out_report)
|
| 800 |
+
|
| 801 |
+
# Ensure we have at least some predictions
|
| 802 |
+
if not results:
|
| 803 |
+
st.warning("Все модели вернули NaN. Использую простой наивный прогноз.")
|
| 804 |
+
for h in horizons:
|
| 805 |
+
pred_values = np.full(h, y_train.iloc[-1] if len(y_train) > 0 else 0)
|
| 806 |
+
pred_dates = create_forecast_index(y_train.index[-1], h, inferred_freq)
|
| 807 |
+
results.append({
|
| 808 |
+
'model': 'fallback_naive',
|
| 809 |
+
'h': h,
|
| 810 |
+
'pred': pd.Series(pred_values, index=pred_dates)
|
| 811 |
+
})
|
| 812 |
+
|
| 813 |
+
|
| 814 |
+
# -------------------------
|
| 815 |
+
# helpers used in the pipeline but defined later
|
| 816 |
+
# -------------------------
|
| 817 |
+
def seasonal_naive_forecast(series: pd.Series, season: int, steps: int):
|
| 818 |
+
last = series.iloc[-season:]
|
| 819 |
+
reps = int(np.ceil(steps / season))
|
| 820 |
+
arr = np.tile(last.values, reps)[:steps]
|
| 821 |
+
return arr
|
| 822 |
+
|
| 823 |
+
|
| 824 |
+
def create_forecast_index(last_train_date: pd.Timestamp, steps: int, freq: str = 'D') -> pd.DatetimeIndex:
|
| 825 |
+
"""Создает правильный временной индекс для прогнозов"""
|
| 826 |
+
try:
|
| 827 |
+
# Если freq = 'auto', пытаемся определить частоту
|
| 828 |
+
if freq == 'auto':
|
| 829 |
+
freq = pd.infer_freq(pd.DatetimeIndex([last_train_date])) or 'D'
|
| 830 |
+
|
| 831 |
+
# Создаем индекс с правильным смещением
|
| 832 |
+
if isinstance(last_train_date, pd.Timestamp):
|
| 833 |
+
start_date = last_train_date + pd.Timedelta(days=1)
|
| 834 |
+
else:
|
| 835 |
+
start_date = last_train_date + pd.DateOffset(days=1)
|
| 836 |
+
|
| 837 |
+
return pd.date_range(
|
| 838 |
+
start=start_date,
|
| 839 |
+
periods=steps,
|
| 840 |
+
freq=freq
|
| 841 |
+
)
|
| 842 |
+
except Exception as e:
|
| 843 |
+
print(f"Warning: could not create proper date index: {e}")
|
| 844 |
+
# Fallback: числовой индекс
|
| 845 |
+
return pd.RangeIndex(start=0, stop=steps)
|
src/streamlit_app.py
CHANGED
|
@@ -1505,26 +1505,28 @@ def render_lab2():
|
|
| 1505 |
|
| 1506 |
color = colors[color_idx % len(colors)]
|
| 1507 |
|
|
|
|
| 1508 |
# Создаём индексы для прогноза (продолжение после обучающей выборки)
|
| 1509 |
if len(forecast) > 0:
|
| 1510 |
-
# Определяем частоту временного ряда
|
| 1511 |
try:
|
| 1512 |
-
|
| 1513 |
-
|
| 1514 |
-
|
| 1515 |
-
|
| 1516 |
-
|
|
|
|
|
|
|
| 1517 |
forecast_dates = pd.date_range(
|
| 1518 |
-
start=
|
| 1519 |
-
periods=len(forecast)
|
| 1520 |
freq=freq
|
| 1521 |
-
)
|
| 1522 |
except Exception as e:
|
| 1523 |
-
#
|
| 1524 |
try:
|
| 1525 |
forecast_dates = s_test_original.index[:len(forecast)]
|
| 1526 |
except:
|
| 1527 |
-
# Последний вариант -
|
| 1528 |
forecast_dates = range(len(s_train_original), len(s_train_original) + len(forecast))
|
| 1529 |
|
| 1530 |
# Добавляем доверительные интервалы, если они есть
|
|
@@ -1578,32 +1580,28 @@ def render_lab2():
|
|
| 1578 |
))
|
| 1579 |
|
| 1580 |
color_idx += 1
|
| 1581 |
-
|
| 1582 |
# Наивный прогноз (если есть)
|
| 1583 |
if 'Naive' in all_forecasts:
|
| 1584 |
naive_forecast_vals = all_forecasts['Naive']
|
| 1585 |
try:
|
| 1586 |
-
|
| 1587 |
-
|
| 1588 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1589 |
naive_dates = pd.date_range(
|
| 1590 |
-
start=
|
| 1591 |
-
periods=len(naive_forecast_vals)
|
| 1592 |
freq=freq
|
| 1593 |
-
)
|
| 1594 |
except Exception as e:
|
| 1595 |
try:
|
| 1596 |
naive_dates = s_test_original.index[:len(naive_forecast_vals)]
|
| 1597 |
except:
|
| 1598 |
naive_dates = range(len(s_train_original), len(s_train_original) + len(naive_forecast_vals))
|
| 1599 |
-
|
| 1600 |
-
fig.add_trace(go.Scatter(
|
| 1601 |
-
x=naive_dates,
|
| 1602 |
-
y=naive_forecast_vals,
|
| 1603 |
-
name='Naive (прогноз)',
|
| 1604 |
-
line=dict(dash='dot', color='gray', width=2),
|
| 1605 |
-
mode='lines'
|
| 1606 |
-
))
|
| 1607 |
|
| 1608 |
# Вертикальная линия, разделяющая train и test
|
| 1609 |
if len(s_train_original) > 0:
|
|
@@ -1927,6 +1925,554 @@ import streamlit.components.v1 as components
|
|
| 1927 |
import pandas as pd
|
| 1928 |
from pathlib import Path
|
| 1929 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1930 |
def render_lab3():
|
| 1931 |
st.title("🧪 ЛР №3: Сравнительный анализ классических моделей")
|
| 1932 |
|
|
@@ -2169,7 +2715,11 @@ def render_lab3():
|
|
| 2169 |
out_report = st.text_input("Путь для отчёта (HTML)", "./lab3_report.html")
|
| 2170 |
freq = st.selectbox("Частота ресемплинга", ['D','W','M'])
|
| 2171 |
|
| 2172 |
-
if st.button("Запустить полный pipeline и сформировать отчёт"):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2173 |
tmp = "./_lab3_input_tmp.csv"
|
| 2174 |
# сохраняем df с колонкой timestamp
|
| 2175 |
df.to_csv(tmp, index=False)
|
|
|
|
| 1505 |
|
| 1506 |
color = colors[color_idx % len(colors)]
|
| 1507 |
|
| 1508 |
+
# Создаём индексы для прогноза (продолжение после обучающей выборки)
|
| 1509 |
# Создаём индексы для прогноза (продолжение после обучающей выборки)
|
| 1510 |
if len(forecast) > 0:
|
|
|
|
| 1511 |
try:
|
| 1512 |
+
# Правильное создание временных индексов для прогнозов
|
| 1513 |
+
if hasattr(s_train_original.index, 'freq') and s_train_original.index.freq is not None:
|
| 1514 |
+
freq = s_train_original.index.freq
|
| 1515 |
+
else:
|
| 1516 |
+
freq = pd.infer_freq(s_train_original.index) or 'D'
|
| 1517 |
+
|
| 1518 |
+
last_date = s_train_original.index[-1]
|
| 1519 |
forecast_dates = pd.date_range(
|
| 1520 |
+
start=last_date + pd.Timedelta(days=1),
|
| 1521 |
+
periods=len(forecast),
|
| 1522 |
freq=freq
|
| 1523 |
+
)
|
| 1524 |
except Exception as e:
|
| 1525 |
+
# Fallback: используем индексы тестовой выборки
|
| 1526 |
try:
|
| 1527 |
forecast_dates = s_test_original.index[:len(forecast)]
|
| 1528 |
except:
|
| 1529 |
+
# Последний вариант - простой числовой индекс
|
| 1530 |
forecast_dates = range(len(s_train_original), len(s_train_original) + len(forecast))
|
| 1531 |
|
| 1532 |
# Добавляем доверительные интервалы, если они есть
|
|
|
|
| 1580 |
))
|
| 1581 |
|
| 1582 |
color_idx += 1
|
| 1583 |
+
|
| 1584 |
# Наивный прогноз (если есть)
|
| 1585 |
if 'Naive' in all_forecasts:
|
| 1586 |
naive_forecast_vals = all_forecasts['Naive']
|
| 1587 |
try:
|
| 1588 |
+
# Правильное создание временных индексов для прогнозов
|
| 1589 |
+
if hasattr(s_train_original.index, 'freq') and s_train_original.index.freq is not None:
|
| 1590 |
+
freq = s_train_original.index.freq
|
| 1591 |
+
else:
|
| 1592 |
+
freq = pd.infer_freq(s_train_original.index) or 'D'
|
| 1593 |
+
|
| 1594 |
+
last_date = s_train_original.index[-1]
|
| 1595 |
naive_dates = pd.date_range(
|
| 1596 |
+
start=last_date + pd.Timedelta(days=1),
|
| 1597 |
+
periods=len(naive_forecast_vals),
|
| 1598 |
freq=freq
|
| 1599 |
+
)
|
| 1600 |
except Exception as e:
|
| 1601 |
try:
|
| 1602 |
naive_dates = s_test_original.index[:len(naive_forecast_vals)]
|
| 1603 |
except:
|
| 1604 |
naive_dates = range(len(s_train_original), len(s_train_original) + len(naive_forecast_vals))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1605 |
|
| 1606 |
# Вертикальная линия, разделяющая train и test
|
| 1607 |
if len(s_train_original) > 0:
|
|
|
|
| 1925 |
import pandas as pd
|
| 1926 |
from pathlib import Path
|
| 1927 |
|
| 1928 |
+
# -----------------------------
|
| 1929 |
+
# Streamlit: inline pipeline visualizer
|
| 1930 |
+
# -----------------------------
|
| 1931 |
+
|
| 1932 |
+
# inverse helpers (вставь в display_pipeline_inline или в lab3_pipeline)
|
| 1933 |
+
import numpy as _np
|
| 1934 |
+
|
| 1935 |
+
def inverse_boxcox_arr(arr, lmbda):
|
| 1936 |
+
arr = _np.array(arr, dtype=float)
|
| 1937 |
+
if lmbda is None:
|
| 1938 |
+
return arr
|
| 1939 |
+
if abs(lmbda) < 1e-8:
|
| 1940 |
+
return _np.exp(arr)
|
| 1941 |
+
else:
|
| 1942 |
+
return _np.power(lmbda * arr + 1.0, 1.0 / lmbda)
|
| 1943 |
+
|
| 1944 |
+
|
| 1945 |
+
def inverse_transform_preds(preds, meta, orig_series):
|
| 1946 |
+
"""
|
| 1947 |
+
Правильное инвертирование преобразований для прогнозов
|
| 1948 |
+
"""
|
| 1949 |
+
preds = np.array(preds, dtype=float)
|
| 1950 |
+
|
| 1951 |
+
# Если нет преобразований, возвращаем как есть
|
| 1952 |
+
if meta.get('method') == 'none' and meta.get('diff_order', 0) == 0:
|
| 1953 |
+
return preds
|
| 1954 |
+
|
| 1955 |
+
# Получаем информацию о преобразованиях
|
| 1956 |
+
method = meta.get('method', 'none')
|
| 1957 |
+
lam = meta.get('lambda', None)
|
| 1958 |
+
diff_order = meta.get('diff_order', 0)
|
| 1959 |
+
|
| 1960 |
+
# Шаг 1: Обратное дифференцирование (если было)
|
| 1961 |
+
if diff_order > 0:
|
| 1962 |
+
# Для обратного дифференцирования нужны последние значения исходного ряда
|
| 1963 |
+
orig_values = orig_series.dropna().values
|
| 1964 |
+
|
| 1965 |
+
if len(orig_values) < diff_order:
|
| 1966 |
+
# Недостаточно данных для обратного дифференцирования
|
| 1967 |
+
return preds
|
| 1968 |
+
|
| 1969 |
+
# Простое обратное дифференцирование первого порядка
|
| 1970 |
+
if diff_order == 1:
|
| 1971 |
+
last_value = float(orig_values[-1])
|
| 1972 |
+
reconstructed = []
|
| 1973 |
+
for i, diff_val in enumerate(preds):
|
| 1974 |
+
if i == 0:
|
| 1975 |
+
reconstructed.append(last_value + diff_val)
|
| 1976 |
+
else:
|
| 1977 |
+
reconstructed.append(reconstructed[-1] + diff_val)
|
| 1978 |
+
preds = np.array(reconstructed)
|
| 1979 |
+
|
| 1980 |
+
# Шаг 2: Обратное преобразование Бокса-Кокса или логарифма
|
| 1981 |
+
if method.startswith('boxcox'):
|
| 1982 |
+
if lam is None:
|
| 1983 |
+
lam = 0.0
|
| 1984 |
+
# Обратное преобразование Бокса-Кокса
|
| 1985 |
+
if abs(lam) < 1e-8:
|
| 1986 |
+
preds = np.exp(preds)
|
| 1987 |
+
else:
|
| 1988 |
+
preds = np.power(lam * preds + 1, 1.0 / lam)
|
| 1989 |
+
elif method == 'log':
|
| 1990 |
+
preds = np.exp(preds)
|
| 1991 |
+
|
| 1992 |
+
return preds
|
| 1993 |
+
|
| 1994 |
+
|
| 1995 |
+
def walk_forward_one_step_preds(model_label, model_obj, train_series, test_series, extra=None):
|
| 1996 |
+
"""
|
| 1997 |
+
Вернёт numpy array длины len(test_series) с one-step прогнозами.
|
| 1998 |
+
"""
|
| 1999 |
+
import numpy as _np
|
| 2000 |
+
preds = []
|
| 2001 |
+
history = train_series.copy() # pd.Series
|
| 2002 |
+
|
| 2003 |
+
for i in range(len(test_series)):
|
| 2004 |
+
try:
|
| 2005 |
+
if model_label == 'sarimax':
|
| 2006 |
+
# Для SARIMAX используем предобученную модель и делаем только прогноз
|
| 2007 |
+
# Не переобучаем модель на каждом шаге
|
| 2008 |
+
if i == 0:
|
| 2009 |
+
# Только на первом шаге используем обученную модель
|
| 2010 |
+
order = model_obj.get('order', (1, 1, 1)) if isinstance(model_obj, dict) else (1, 1, 1)
|
| 2011 |
+
seasonal_order = model_obj.get('seasonal_order', (0, 0, 0, 0)) if isinstance(model_obj, dict) else (
|
| 2012 |
+
0, 0, 0, 0)
|
| 2013 |
+
|
| 2014 |
+
if len(history.dropna()) < 10:
|
| 2015 |
+
pred_value = float(history.dropna().iloc[-1]) if len(history.dropna()) > 0 else 0.0
|
| 2016 |
+
preds.append(pred_value)
|
| 2017 |
+
history = pd.concat([history, pd.Series([test_series.iloc[i]], index=[test_series.index[i]])])
|
| 2018 |
+
continue
|
| 2019 |
+
|
| 2020 |
+
# Обучаем модель один раз на обучающих данных
|
| 2021 |
+
m = SARIMAX(history.dropna(), order=order, seasonal_order=seasonal_order,
|
| 2022 |
+
enforce_stationarity=False, enforce_invertibility=False)
|
| 2023 |
+
fitted_model = m.fit(disp=False, maxiter=50)
|
| 2024 |
+
|
| 2025 |
+
# Делаем прогноз на один шаг вперед
|
| 2026 |
+
forecast_result = fitted_model.get_forecast(steps=1)
|
| 2027 |
+
pred_value = float(forecast_result.predicted_mean.iloc[0])
|
| 2028 |
+
|
| 2029 |
+
# Проверяем на NaN/Inf
|
| 2030 |
+
if np.isnan(pred_value) or np.isinf(pred_value):
|
| 2031 |
+
pred_value = float(history.dropna().iloc[-1])
|
| 2032 |
+
|
| 2033 |
+
preds.append(pred_value)
|
| 2034 |
+
|
| 2035 |
+
# НЕ добавляем тестовые данные в историю для переобучения
|
| 2036 |
+
# Это предотвращает "подглядывание" в будущее
|
| 2037 |
+
# history остается неизменной после начального обучения
|
| 2038 |
+
|
| 2039 |
+
elif model_label == 'auto_arima':
|
| 2040 |
+
try:
|
| 2041 |
+
# Для auto_arima также обучаем один раз
|
| 2042 |
+
if i == 0:
|
| 2043 |
+
import pmdarima as pm_local
|
| 2044 |
+
if len(history.dropna()) < 10:
|
| 2045 |
+
pred_value = float(history.dropna().iloc[-1])
|
| 2046 |
+
else:
|
| 2047 |
+
auto_model = pm_local.auto_arima(history.dropna(), seasonal=False, error_action='ignore',
|
| 2048 |
+
suppress_warnings=True, maxiter=50)
|
| 2049 |
+
|
| 2050 |
+
if i == 0 or not hasattr(auto_model, 'update'):
|
| 2051 |
+
p = auto_model.predict(n_periods=1)
|
| 2052 |
+
else:
|
| 2053 |
+
# Обновляем модель с новыми данными (если поддерживается)
|
| 2054 |
+
auto_model.update(pd.Series([test_series.iloc[i - 1]]))
|
| 2055 |
+
p = auto_model.predict(n_periods=1)
|
| 2056 |
+
|
| 2057 |
+
pred_value = float(p[0]) if hasattr(p, '__iter__') else float(p)
|
| 2058 |
+
|
| 2059 |
+
# Проверяем на NaN/Inf
|
| 2060 |
+
if np.isnan(pred_value) or np.isinf(pred_value):
|
| 2061 |
+
pred_value = float(history.dropna().iloc[-1])
|
| 2062 |
+
|
| 2063 |
+
preds.append(pred_value)
|
| 2064 |
+
except Exception as e:
|
| 2065 |
+
pred_value = float(history.dropna().iloc[-1]) if len(history.dropna()) > 0 else 0.0
|
| 2066 |
+
preds.append(pred_value)
|
| 2067 |
+
|
| 2068 |
+
elif model_label == 'SES':
|
| 2069 |
+
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
|
| 2070 |
+
try:
|
| 2071 |
+
# Для SES также обучаем один раз
|
| 2072 |
+
if i == 0:
|
| 2073 |
+
if len(history.dropna()) < 2:
|
| 2074 |
+
ses_model = None
|
| 2075 |
+
else:
|
| 2076 |
+
ses_model = SimpleExpSmoothing(history.dropna()).fit(optimized=True)
|
| 2077 |
+
|
| 2078 |
+
if ses_model is None:
|
| 2079 |
+
pred_value = float(history.dropna().iloc[-1])
|
| 2080 |
+
else:
|
| 2081 |
+
p = ses_model.forecast(1)
|
| 2082 |
+
pred_value = float(p.iloc[0]) if hasattr(p, 'iloc') else float(p[0])
|
| 2083 |
+
|
| 2084 |
+
# Проверяем на NaN/Inf
|
| 2085 |
+
if np.isnan(pred_value) or np.isinf(pred_value):
|
| 2086 |
+
pred_value = float(history.dropna().iloc[-1])
|
| 2087 |
+
|
| 2088 |
+
preds.append(pred_value)
|
| 2089 |
+
except Exception as e:
|
| 2090 |
+
pred_value = float(history.dropna().iloc[-1]) if len(history.dropna()) > 0 else 0.0
|
| 2091 |
+
preds.append(pred_value)
|
| 2092 |
+
|
| 2093 |
+
else:
|
| 2094 |
+
# naive: forecast last observed
|
| 2095 |
+
pred_value = float(history.dropna().iloc[-1]) if len(history.dropna()) > 0 else 0.0
|
| 2096 |
+
preds.append(pred_value)
|
| 2097 |
+
|
| 2098 |
+
except Exception as e:
|
| 2099 |
+
# fallback: append last value
|
| 2100 |
+
try:
|
| 2101 |
+
pred_value = float(history.dropna().iloc[-1]) if len(history.dropna()) > 0 else 0.0
|
| 2102 |
+
except:
|
| 2103 |
+
pred_value = 0.0
|
| 2104 |
+
preds.append(pred_value)
|
| 2105 |
+
|
| 2106 |
+
return _np.array(preds, dtype=float)
|
| 2107 |
+
|
| 2108 |
+
|
| 2109 |
+
def display_pipeline_inline(data_path: str, timestamp_col: str, target_col: str, freq: str = 'D'):
|
| 2110 |
+
"""
|
| 2111 |
+
Загружает данные через lab3_pipeline helpers и отображает ключевые этапы
|
| 2112 |
+
"""
|
| 2113 |
+
import importlib
|
| 2114 |
+
try:
|
| 2115 |
+
import lab3_pipeline as lp
|
| 2116 |
+
importlib.reload(lp)
|
| 2117 |
+
# Определяем PM_AVAILABLE из pipeline
|
| 2118 |
+
PM_AVAILABLE = lp.PM_AVAILABLE
|
| 2119 |
+
STATSMODELS_AVAILABLE = lp.STATSMODELS_AVAILABLE
|
| 2120 |
+
except Exception as e:
|
| 2121 |
+
st.error("Не удалось импортировать lab3_pipeline: " + str(e))
|
| 2122 |
+
return
|
| 2123 |
+
|
| 2124 |
+
st.info("Загружаю данные...")
|
| 2125 |
+
try:
|
| 2126 |
+
df = lp.load_data(data_path, timestamp_col)
|
| 2127 |
+
except Exception as e:
|
| 2128 |
+
st.error("Ошибка загрузки данных: " + str(e))
|
| 2129 |
+
return
|
| 2130 |
+
|
| 2131 |
+
# постараемся получить series
|
| 2132 |
+
if target_col not in df.columns:
|
| 2133 |
+
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
|
| 2134 |
+
if not numeric_cols:
|
| 2135 |
+
st.error("Не найдено числовых колонок для target.")
|
| 2136 |
+
return
|
| 2137 |
+
target_col = numeric_cols[0]
|
| 2138 |
+
st.warning(f"Target не найден — использую {target_col}.")
|
| 2139 |
+
|
| 2140 |
+
series = df[target_col].astype(float).copy()
|
| 2141 |
+
|
| 2142 |
+
# Проверяем данные на наличие NaN и Inf
|
| 2143 |
+
if series.isna().all():
|
| 2144 |
+
st.error("Все значения в целевом ряду NaN. Проверьте данные.")
|
| 2145 |
+
return
|
| 2146 |
+
|
| 2147 |
+
if (series == 0).all():
|
| 2148 |
+
st.warning("Все значения в целевом ряду равны 0. Это может вызвать проблемы с моделями.")
|
| 2149 |
+
|
| 2150 |
+
# Заполняем пропуски простым методом
|
| 2151 |
+
series = series.fillna(method='ffill').fillna(method='bfill').fillna(0)
|
| 2152 |
+
|
| 2153 |
+
st.write("Исходный ряд — первые и последние точки:")
|
| 2154 |
+
st.write(pd.concat([series.head(5), series.tail(5)]).to_frame(target_col))
|
| 2155 |
+
|
| 2156 |
+
# ресемплим/интерполируем
|
| 2157 |
+
try:
|
| 2158 |
+
series_rs = lp.resample_and_interpolate(series.to_frame(), freq=freq).iloc[:, 0]
|
| 2159 |
+
# Снова проверяем и заполняем пропуски после ресемплинга
|
| 2160 |
+
series_rs = series_rs.fillna(method='ffill').fillna(method='bfill').fillna(0)
|
| 2161 |
+
except Exception as e:
|
| 2162 |
+
st.warning("Resample/interpolate failed, using original: " + str(e))
|
| 2163 |
+
series_rs = series
|
| 2164 |
+
|
| 2165 |
+
st.subheader("1) Временной ряд")
|
| 2166 |
+
fig, ax = plt.subplots(figsize=(10,3))
|
| 2167 |
+
ax.plot(series_rs.index, series_rs.values, label='series')
|
| 2168 |
+
ax.set_title("Временной ряд (после ресемплинга)")
|
| 2169 |
+
ax.legend()
|
| 2170 |
+
st.pyplot(fig)
|
| 2171 |
+
|
| 2172 |
+
# 3.1: transformations selection
|
| 2173 |
+
st.subheader("2) Подбор преобразований (log / Box-Cox / differencing)")
|
| 2174 |
+
try:
|
| 2175 |
+
transformed, meta = lp.try_transformations_and_choose(series_rs, seasonal_period=7)
|
| 2176 |
+
st.write("Выбранное преобразование:", meta)
|
| 2177 |
+
except Exception as e:
|
| 2178 |
+
st.warning("Не удалось выполнить подбор преобразований: " + str(e))
|
| 2179 |
+
transformed, meta = series_rs, {'method': 'none'}
|
| 2180 |
+
|
| 2181 |
+
# Покажем original vs transformed
|
| 2182 |
+
fig, ax = plt.subplots(1,1,figsize=(10,3))
|
| 2183 |
+
ax.plot(series_rs.index[-200:], series_rs.values[-200:], label='original')
|
| 2184 |
+
ax.plot(transformed.index[-200:], transformed.values[-200:], label='transformed')
|
| 2185 |
+
ax.set_title("Original vs Transformed (последние 200 точек)")
|
| 2186 |
+
ax.legend()
|
| 2187 |
+
st.pyplot(fig)
|
| 2188 |
+
|
| 2189 |
+
# stationarity tests
|
| 2190 |
+
st.subheader("3) Тесты на стационарность (ADF, KPSS)")
|
| 2191 |
+
try:
|
| 2192 |
+
tests_orig = lp.test_stationarity_pair(series_rs.fillna(method='ffill'))
|
| 2193 |
+
tests_trans = lp.test_stationarity_pair(transformed.fillna(method='ffill'))
|
| 2194 |
+
st.write("Оригинал:", tests_orig)
|
| 2195 |
+
st.write("Преобразованный ряд:", tests_trans)
|
| 2196 |
+
except Exception as e:
|
| 2197 |
+
st.warning("Тесты стационарности не выполнены: " + str(e))
|
| 2198 |
+
|
| 2199 |
+
# decomposition
|
| 2200 |
+
st.subheader("4) Декомпозиция ряда (additive)")
|
| 2201 |
+
try:
|
| 2202 |
+
if lp.STATSMODELS_AVAILABLE:
|
| 2203 |
+
dec = lp.seasonal_decompose(transformed.dropna(), model='additive', period=7)
|
| 2204 |
+
fig = dec.plot()
|
| 2205 |
+
fig.set_size_inches(10,6)
|
| 2206 |
+
st.pyplot(fig)
|
| 2207 |
+
else:
|
| 2208 |
+
st.info("statsmodels не доступен: декомпозиция пропущена.")
|
| 2209 |
+
except Exception as e:
|
| 2210 |
+
st.warning("Ошибка декомпозиции: " + str(e))
|
| 2211 |
+
|
| 2212 |
+
# ACF/PACF
|
| 2213 |
+
st.subheader("5) ACF / PACF")
|
| 2214 |
+
try:
|
| 2215 |
+
if lp.STATSMODELS_AVAILABLE:
|
| 2216 |
+
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
|
| 2217 |
+
|
| 2218 |
+
ser_ac = transformed.dropna()
|
| 2219 |
+
if len(ser_ac) < 3:
|
| 2220 |
+
st.info("Недостаточно точек для ACF/PACF (нужно >=3).")
|
| 2221 |
+
else:
|
| 2222 |
+
# ACF
|
| 2223 |
+
fig_acf, ax_acf = plt.subplots(figsize=(10, 3))
|
| 2224 |
+
plot_acf(ser_ac, lags=40, ax=ax_acf, zero=False)
|
| 2225 |
+
ax_acf.set_title("ACF")
|
| 2226 |
+
st.pyplot(fig_acf)
|
| 2227 |
+
plt.close(fig_acf)
|
| 2228 |
+
|
| 2229 |
+
# PACF
|
| 2230 |
+
fig_pacf, ax_pacf = plt.subplots(figsize=(10, 3))
|
| 2231 |
+
# метод pacf можно изменить в зависимости от версии statsmodels ('ywm', 'ld', 'ols', ...)
|
| 2232 |
+
try:
|
| 2233 |
+
plot_pacf(ser_ac, lags=40, ax=ax_pacf, zero=False, method='ywm')
|
| 2234 |
+
except Exception:
|
| 2235 |
+
plot_pacf(ser_ac, lags=40, ax=ax_pacf, zero=False)
|
| 2236 |
+
ax_pacf.set_title("PACF")
|
| 2237 |
+
st.pyplot(fig_pacf)
|
| 2238 |
+
plt.close(fig_pacf)
|
| 2239 |
+
else:
|
| 2240 |
+
st.info("statsmodels не доступен — ACF/PACF пропущены.")
|
| 2241 |
+
except Exception as e:
|
| 2242 |
+
st.warning("ACF/PACF error: " + str(e))
|
| 2243 |
+
|
| 2244 |
+
# 3.2 feature engineering demonstration
|
| 2245 |
+
st.subheader("6) Feature engineering (лаги, скользящие, временные признаки)")
|
| 2246 |
+
try:
|
| 2247 |
+
df_all = transformed.to_frame(name=target_col)
|
| 2248 |
+
df_all = lp.make_time_features(df_all)
|
| 2249 |
+
df_all = lp.make_lags(df_all, target_col, [1,2,7,30])
|
| 2250 |
+
df_all = lp.make_rolls(df_all, target_col, [7,30])
|
| 2251 |
+
st.write("Превью признаков:")
|
| 2252 |
+
st.dataframe(df_all.dropna().head(10))
|
| 2253 |
+
except Exception as e:
|
| 2254 |
+
st.warning("Ошибка при генерации признаков: " + str(e))
|
| 2255 |
+
|
| 2256 |
+
# split
|
| 2257 |
+
st.subheader("7) Разбиение train/val/test")
|
| 2258 |
+
try:
|
| 2259 |
+
df_all = df_all.dropna()
|
| 2260 |
+
train, val, test = lp.chronological_split(df_all, frac_train=0.7, frac_val=0)
|
| 2261 |
+
st.write("Размеры:", {'train': len(train), 'val': len(val), 'test': len(test)})
|
| 2262 |
+
except Exception as e:
|
| 2263 |
+
st.warning("Ошибка разбиения: " + str(e))
|
| 2264 |
+
return
|
| 2265 |
+
|
| 2266 |
+
# 3.3-3.5 Модели: несколько базовых (быстро и наглядно)
|
| 2267 |
+
st.subheader("8) Быстрое обучение моделей и прогнозы (h=1,7,30)")
|
| 2268 |
+
horizons = [1,7,30]
|
| 2269 |
+
results = []
|
| 2270 |
+
# benchmarks
|
| 2271 |
+
for h in horizons:
|
| 2272 |
+
try:
|
| 2273 |
+
results.append({'model':'naive','h':h,'pred': lp.naive_forecast(train[target_col], h)})
|
| 2274 |
+
if len(train[target_col])>=7:
|
| 2275 |
+
results.append({'model':'seasonal_naive','h':h, 'pred': lp.seasonal_naive_forecast(train[target_col], season=7, steps=h)})
|
| 2276 |
+
except Exception:
|
| 2277 |
+
pass
|
| 2278 |
+
|
| 2279 |
+
# SES (если доступен)
|
| 2280 |
+
try:
|
| 2281 |
+
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
|
| 2282 |
+
ses = SimpleExpSmoothing(train[target_col].dropna()).fit(optimized=True)
|
| 2283 |
+
for h in horizons:
|
| 2284 |
+
results.append({'model':'SES','h':h,'pred': np.asarray(ses.forecast(h))})
|
| 2285 |
+
except Exception:
|
| 2286 |
+
pass
|
| 2287 |
+
|
| 2288 |
+
# SARIMAX baseline
|
| 2289 |
+
try:
|
| 2290 |
+
sar = lp.fit_sarimax_simple(train[target_col], order=(1,1,1))
|
| 2291 |
+
for h in horizons:
|
| 2292 |
+
p, ci = lp.forecast_sarimax(sar, steps=h)
|
| 2293 |
+
results.append({'model':'SARIMAX(1,1,1)','h':h,'pred': np.asarray(p), 'ci': ci})
|
| 2294 |
+
except Exception as e:
|
| 2295 |
+
st.warning("SARIMAX skipped: " + str(e))
|
| 2296 |
+
|
| 2297 |
+
# pmdarima auto_arima
|
| 2298 |
+
try:
|
| 2299 |
+
if PM_AVAILABLE: # Теперь эта переменная определена
|
| 2300 |
+
auto = lp.fit_auto_arima(train[target_col], seasonal=False)
|
| 2301 |
+
for h in horizons:
|
| 2302 |
+
results.append({'model':'auto_arima','h':h,'pred': np.asarray(auto.predict(n_periods=h))})
|
| 2303 |
+
except Exception:
|
| 2304 |
+
pass
|
| 2305 |
+
|
| 2306 |
+
# VAR if multivariate
|
| 2307 |
+
try:
|
| 2308 |
+
if lp.STATSMODELS_AVAILABLE and df.select_dtypes(include=[np.number]).shape[1] >= 2:
|
| 2309 |
+
num_df = df.select_dtypes(include=[np.number]).dropna()
|
| 2310 |
+
var_res = lp.fit_var(num_df.iloc[:int(len(num_df)*0.7)], maxlags=5)
|
| 2311 |
+
fut = lp.forecast_var(var_res, steps=30)
|
| 2312 |
+
results.append({'model':'VAR','h':30,'pred': np.asarray(fut.iloc[:,0])})
|
| 2313 |
+
except Exception:
|
| 2314 |
+
pass
|
| 2315 |
+
|
| 2316 |
+
# Evaluate models on test
|
| 2317 |
+
st.subheader("9) Оценка моделей на тесте")
|
| 2318 |
+
eval_rows = []
|
| 2319 |
+
for rec in results:
|
| 2320 |
+
pred = np.asarray(rec['pred']).ravel()
|
| 2321 |
+
y_true = test[target_col].values[:len(pred)]
|
| 2322 |
+
if len(y_true)==0:
|
| 2323 |
+
continue
|
| 2324 |
+
row = {
|
| 2325 |
+
'model': rec['model'],
|
| 2326 |
+
'h': rec['h'],
|
| 2327 |
+
'MAE': float(lp.mae(y_true, pred)),
|
| 2328 |
+
'RMSE': float(lp.rmse(y_true, pred)),
|
| 2329 |
+
'MAPE': float(lp.mape(y_true, pred))
|
| 2330 |
+
}
|
| 2331 |
+
eval_rows.append(row)
|
| 2332 |
+
if eval_rows:
|
| 2333 |
+
eval_df = pd.DataFrame(eval_rows)
|
| 2334 |
+
st.dataframe(eval_df.sort_values(['h','RMSE']))
|
| 2335 |
+
else:
|
| 2336 |
+
st.info("Нет результатов для оценки.")
|
| 2337 |
+
|
| 2338 |
+
# Получим walk-forward прогнозы для каждого обученного метода (если модель была навчена)
|
| 2339 |
+
# Получим walk-forward прогнозы для каждого обученного метода
|
| 2340 |
+
st.subheader("10) Полная визуализация прогнозов по тесту (walk-forward one-step)")
|
| 2341 |
+
|
| 2342 |
+
# выберем модели, которые были обучены
|
| 2343 |
+
for rec in results:
|
| 2344 |
+
model_name = rec['model']
|
| 2345 |
+
# только те, для которых хотим full series
|
| 2346 |
+
if model_name not in ('SARIMAX(1,1,1)', 'auto_arima', 'SES', 'naive', 'seasonal_naive'):
|
| 2347 |
+
continue
|
| 2348 |
+
st.write("Обрабатываем модель:", model_name)
|
| 2349 |
+
|
| 2350 |
+
# Подготавливаем параметры модели для walk_forward
|
| 2351 |
+
model_args = {}
|
| 2352 |
+
if model_name.startswith('SARIMAX'):
|
| 2353 |
+
model_args = {'order': (1, 1, 1), 'seasonal_order': (0, 0, 0, 0)}
|
| 2354 |
+
model_type = 'sarimax'
|
| 2355 |
+
elif model_name == 'auto_arima' and PM_AVAILABLE:
|
| 2356 |
+
model_type = 'auto_arima'
|
| 2357 |
+
elif model_name == 'SES':
|
| 2358 |
+
model_type = 'SES'
|
| 2359 |
+
elif model_name == 'naive':
|
| 2360 |
+
model_type = 'naive'
|
| 2361 |
+
elif model_name == 'seasonal_naive':
|
| 2362 |
+
model_type = 'naive' # используем naive как fallback
|
| 2363 |
+
else:
|
| 2364 |
+
continue
|
| 2365 |
+
|
| 2366 |
+
# вызов walk_forward для этой модели
|
| 2367 |
+
try:
|
| 2368 |
+
full_preds = walk_forward_one_step_preds(model_type, model_args, train[target_col], test[target_col])
|
| 2369 |
+
|
| 2370 |
+
# Отладочная информация
|
| 2371 |
+
st.write(f"Длина прогнозов: {len(full_preds)}")
|
| 2372 |
+
st.write(f"Количество NaN в прогнозах: {np.isnan(full_preds).sum()}")
|
| 2373 |
+
|
| 2374 |
+
if len(full_preds) > 0 and not np.all(np.isnan(full_preds)):
|
| 2375 |
+
st.write("Пример первых 5 raw preds:", full_preds[:5].tolist())
|
| 2376 |
+
|
| 2377 |
+
# инвертируем трансформации, если они были
|
| 2378 |
+
try:
|
| 2379 |
+
preds_inv = inverse_transform_preds(full_preds, meta, series_rs)
|
| 2380 |
+
st.write("Пример первых 5 инвертированных preds:", preds_inv[:5].tolist())
|
| 2381 |
+
except Exception as e:
|
| 2382 |
+
st.warning(f"inverse_transform failed: {e}")
|
| 2383 |
+
preds_inv = full_preds
|
| 2384 |
+
|
| 2385 |
+
# отрисуем
|
| 2386 |
+
fig, ax = plt.subplots(figsize=(12, 4))
|
| 2387 |
+
|
| 2388 |
+
# history (последние 100 точек для наглядности)
|
| 2389 |
+
hist_points = min(100, len(train))
|
| 2390 |
+
ax.plot(train.index[-hist_points:], train[target_col].values[-hist_points:],
|
| 2391 |
+
label='train (tail)', linewidth=2)
|
| 2392 |
+
|
| 2393 |
+
# test
|
| 2394 |
+
test_to_show = min(len(test), len(full_preds))
|
| 2395 |
+
ax.plot(test.index[:test_to_show], test[target_col].values[:test_to_show],
|
| 2396 |
+
label='test', linewidth=2, alpha=0.7)
|
| 2397 |
+
|
| 2398 |
+
# preds
|
| 2399 |
+
try:
|
| 2400 |
+
ax.plot(test.index[:len(preds_inv)], preds_inv,
|
| 2401 |
+
label=f'pred_{model_name}', linewidth=2, linestyle='--')
|
| 2402 |
+
except Exception as e:
|
| 2403 |
+
ax.plot(range(len(preds_inv)), preds_inv,
|
| 2404 |
+
label=f'pred_{model_name}', linewidth=2, linestyle='--')
|
| 2405 |
+
|
| 2406 |
+
ax.set_title(f'Walk-forward one-step forecasts — {model_name}')
|
| 2407 |
+
ax.legend()
|
| 2408 |
+
ax.grid(True, alpha=0.3)
|
| 2409 |
+
st.pyplot(fig)
|
| 2410 |
+
plt.close(fig)
|
| 2411 |
+
|
| 2412 |
+
# Вычислим метрики качества
|
| 2413 |
+
if len(preds_inv) > 0 and len(test) >= len(preds_inv):
|
| 2414 |
+
y_true = test[target_col].values[:len(preds_inv)]
|
| 2415 |
+
y_pred = preds_inv
|
| 2416 |
+
|
| 2417 |
+
mae_val = np.mean(np.abs(y_true - y_pred))
|
| 2418 |
+
rmse_val = np.sqrt(np.mean((y_true - y_pred) ** 2))
|
| 2419 |
+
|
| 2420 |
+
st.write(f"**Метрики качества для {model_name}:**")
|
| 2421 |
+
st.write(f"- MAE: {mae_val:.4f}")
|
| 2422 |
+
st.write(f"- RMSE: {rmse_val:.4f}")
|
| 2423 |
+
|
| 2424 |
+
else:
|
| 2425 |
+
st.warning(f"Модель {model_name} вернула все NaN прогнозы")
|
| 2426 |
+
|
| 2427 |
+
except Exception as e:
|
| 2428 |
+
st.warning(f"Не удалось посчитать full_preds для {model_name}: {e}")
|
| 2429 |
+
continue
|
| 2430 |
+
|
| 2431 |
+
# Диагностика остатков для SARIMAX (если обучен)
|
| 2432 |
+
st.subheader("11) Диагностика остатков (SARIMAX если есть)")
|
| 2433 |
+
try:
|
| 2434 |
+
if 'sar' in locals():
|
| 2435 |
+
resid = sar.resid.dropna()
|
| 2436 |
+
fig, ax = plt.subplots(figsize=(10,3))
|
| 2437 |
+
ax.plot(resid); ax.set_title("Residuals")
|
| 2438 |
+
st.pyplot(fig)
|
| 2439 |
+
# Ljung-Box
|
| 2440 |
+
try:
|
| 2441 |
+
lb = lp.ljung_box_test(resid, lags=[10])
|
| 2442 |
+
st.write("Ljung-Box (lag=10):")
|
| 2443 |
+
st.dataframe(lb)
|
| 2444 |
+
except Exception:
|
| 2445 |
+
st.info("acorr_ljungbox недоступен")
|
| 2446 |
+
# Shapiro
|
| 2447 |
+
try:
|
| 2448 |
+
sh = lp.shapiro_test(resid)
|
| 2449 |
+
st.write("Shapiro test:", sh)
|
| 2450 |
+
except Exception:
|
| 2451 |
+
st.info("scipy/shapiro недоступен")
|
| 2452 |
+
else:
|
| 2453 |
+
st.info("SARIMAX модель не была обучена — диагностика пропущена.")
|
| 2454 |
+
except Exception as e:
|
| 2455 |
+
st.warning("Error in residual diagnostics: " + str(e))
|
| 2456 |
+
|
| 2457 |
+
# опционально: создать и предложить скачать HTML-отчёт
|
| 2458 |
+
st.subheader("12) Сгенерировать HTML-отчёт")
|
| 2459 |
+
try:
|
| 2460 |
+
if st.button("Сохранить HTML-отчёт (lab3_report.html)"):
|
| 2461 |
+
out_report = "lab3_report.html"
|
| 2462 |
+
lp.generate_report_html(out_report, plots=[], tables={'evaluation': pd.DataFrame(eval_rows)})
|
| 2463 |
+
st.success("Отчёт сохранён: " + out_report)
|
| 2464 |
+
try:
|
| 2465 |
+
with open(out_report, 'r', encoding='utf-8') as f:
|
| 2466 |
+
html = f.read()
|
| 2467 |
+
st.components.v1.html(html, height=600, scrolling=True)
|
| 2468 |
+
except Exception:
|
| 2469 |
+
st.info("Отчёт создан, но не удалось отобразить его в Streamlit.")
|
| 2470 |
+
except Exception:
|
| 2471 |
+
pass
|
| 2472 |
+
|
| 2473 |
+
st.success("Inline pipeline выполнен.")
|
| 2474 |
+
|
| 2475 |
+
|
| 2476 |
def render_lab3():
|
| 2477 |
st.title("🧪 ЛР №3: Сравнительный анализ классических моделей")
|
| 2478 |
|
|
|
|
| 2715 |
out_report = st.text_input("Путь для отчёта (HTML)", "./lab3_report.html")
|
| 2716 |
freq = st.selectbox("Частота ресемплинга", ['D','W','M'])
|
| 2717 |
|
| 2718 |
+
if st.button("Запустить полный pipeline и сформировать отчёт (и показать результаты)"):
|
| 2719 |
+
tmp = "./_lab3_input_tmp.csv"
|
| 2720 |
+
df.to_csv(tmp, index=False)
|
| 2721 |
+
display_pipeline_inline(tmp, timestamp_col='timestamp', target_col=target, freq=freq)
|
| 2722 |
+
|
| 2723 |
tmp = "./_lab3_input_tmp.csv"
|
| 2724 |
# сохраняем df с колонкой timestamp
|
| 2725 |
df.to_csv(tmp, index=False)
|
src/streamlit_lab3.py
DELETED
|
@@ -1,50 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
streamlit_lab3.py
|
| 3 |
-
Упрощённый Streamlit-интерфейс для ЛР №3.
|
| 4 |
-
Позволяет загрузить CSV/Parquet, настроить параметры и запустить pipeline (lab3_pipeline.run_pipeline).
|
| 5 |
-
"""
|
| 6 |
-
|
| 7 |
-
import streamlit as st
|
| 8 |
-
import os
|
| 9 |
-
import pandas as pd
|
| 10 |
-
import tempfile
|
| 11 |
-
from pathlib import Path
|
| 12 |
-
|
| 13 |
-
# импортируем наш pipeline (если он в PATH)
|
| 14 |
-
try:
|
| 15 |
-
from lab3_pipeline import run_pipeline
|
| 16 |
-
PIPELINE_AVAILABLE = True
|
| 17 |
-
except Exception as e:
|
| 18 |
-
PIPELINE_AVAILABLE = False
|
| 19 |
-
st = None
|
| 20 |
-
|
| 21 |
-
def main():
|
| 22 |
-
st.title('ЛР №3 — Временные ряды (pipeline)')
|
| 23 |
-
uploaded = st.file_uploader("Загрузите CSV/Parquet с колонкой timestamp и target", type=['csv','parquet'])
|
| 24 |
-
if uploaded is None:
|
| 25 |
-
st.info("Загрузите файл, или введите путь к локальному файлу на сервере.")
|
| 26 |
-
else:
|
| 27 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=Path(uploaded.name).suffix) as tmp:
|
| 28 |
-
tmp.write(uploaded.getbuffer())
|
| 29 |
-
tmp_path = tmp.name
|
| 30 |
-
st.write("Файл загружен:", tmp_path)
|
| 31 |
-
timestamp = st.text_input("Имя колонки с временной меткой", "timestamp")
|
| 32 |
-
target = st.text_input("Имя колонки с целевой переменной", "target")
|
| 33 |
-
freq = st.selectbox("Частота ресемплинга", ['D','W','M'])
|
| 34 |
-
outpath = './lab3_report.html'
|
| 35 |
-
if st.button("Запустить ЛР3"):
|
| 36 |
-
st.info("Запуск pipeline — может занять время. Выходной файл: " + outpath)
|
| 37 |
-
try:
|
| 38 |
-
run_pipeline(tmp_path, timestamp, target, out_report=outpath, freq=freq)
|
| 39 |
-
with open(outpath, 'r', encoding='utf-8') as f:
|
| 40 |
-
html = f.read()
|
| 41 |
-
st.markdown("### Отчёт")
|
| 42 |
-
st.components.v1.html(html, height=800, scrolling=True)
|
| 43 |
-
except Exception as e:
|
| 44 |
-
st.error("Ошибка при выполнении pipeline: " + str(e))
|
| 45 |
-
|
| 46 |
-
if __name__ == '__main__':
|
| 47 |
-
if PIPELINE_AVAILABLE:
|
| 48 |
-
main()
|
| 49 |
-
else:
|
| 50 |
-
print("lab3_pipeline недоступен. Положите lab3_pipeline.py в PYTHONPATH или /mnt/data")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|