regression.ts 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import differenceInDays from 'date-fns/differenceInDays';
  2. import addDays from 'date-fns/addDays';
  3. import { Cases, Data, FutureCase } from '../types';
  4. // Start the regression line after this many cases have been recorded in total
  5. const regressionStart = 50;
  6. const futureDays = 10;
  7. const mean = (values: number[]): number =>
  8. values.reduce((last, value) => last + value, 0) / values.length;
  9. export function getExponentialRegression(cases: Cases): Data {
  10. if (!cases.length) {
  11. return [];
  12. }
  13. const { index: startIndex } = cases.reduce(
  14. ({ sum, index }, { value }, nextIndex) => {
  15. if (sum >= regressionStart) {
  16. return { sum, index };
  17. }
  18. const nextSum = sum + value;
  19. if (nextSum < regressionStart) {
  20. return { sum: nextSum, index };
  21. }
  22. return { sum: nextSum, index: nextIndex };
  23. },
  24. { sum: 0, index: -1 },
  25. );
  26. if (startIndex === -1) {
  27. return cases.map(({ date, value }) => ({
  28. date: date.getTime(),
  29. value,
  30. }));
  31. }
  32. const casesToRegress = cases.slice(startIndex);
  33. // It's assumed that the input here is ordered by date ascending
  34. const minDate = casesToRegress[0].date;
  35. const xSeries = casesToRegress.map(({ date }) => 1 + differenceInDays(date, minDate));
  36. const ySeries = casesToRegress.map(({ value }) => Math.log(value));
  37. const xBar = mean(xSeries);
  38. const yBar = mean(ySeries);
  39. const covariance =
  40. xSeries.reduce((last, value, index) => last + (value - xBar) * (ySeries[index] - yBar), 0) /
  41. xSeries.length;
  42. const xVariance = mean(xSeries.map(value => value ** 2)) - xBar ** 2;
  43. const slope = covariance / xVariance;
  44. const intercept = yBar - slope * xBar;
  45. const regressionAtDate = (date: Date): number => {
  46. const xValue = differenceInDays(date, minDate);
  47. return Math.exp(slope * xValue + intercept);
  48. };
  49. const lastDate = cases[cases.length - 1].date;
  50. const future: FutureCase[] = new Array(futureDays)
  51. .fill(0)
  52. .map((_, index) => ({ date: addDays(lastDate, index + 1) }));
  53. return [...cases, ...future].map(({ date, ...rest }) => ({
  54. date: date.getTime(),
  55. regression: regressionAtDate(date),
  56. ...rest,
  57. }));
  58. }