ソースを参照

Don't compute regression until at least 50 cumulative cases

Fela Maslen 5 年 前
コミット
a5cdb9f08c
3 ファイル変更56 行追加8 行削除
  1. 11 1
      src/components/graph-cases.tsx
  2. 5 1
      src/types.ts
  3. 40 6
      src/utils/regression.ts

+ 11 - 1
src/components/graph-cases.tsx

@@ -1,4 +1,5 @@
 import React from 'react';
+import format from 'date-fns/format';
 import { LineChart, XAxis, YAxis, Line, CartesianGrid } from 'recharts';
 
 import { Cases, Country, Data } from '../types';
@@ -16,6 +17,10 @@ const margin = {
   left: 0,
 };
 
+const dateTickFormatter = (date: Date): string => {
+  return format(new Date(date), 'LLL do'); // 'Do MMM');
+};
+
 const GraphCases: React.FC<Props> = ({ country }) => {
   const cases = React.useMemo<Cases>(() => getCases(country), [country]);
   const data = React.useMemo<Data>(() => getExponentialRegression(cases), [cases]);
@@ -24,7 +29,12 @@ const GraphCases: React.FC<Props> = ({ country }) => {
     <div>
       <h3>Country: {country.toUpperCase()}</h3>
       <LineChart width={640} height={480} data={data} margin={margin}>
-        <XAxis dataKey="date" label="Date" />
+        <XAxis
+          dataKey="date"
+          domain={['auto', 'auto']}
+          tickFormatter={dateTickFormatter}
+          type="number"
+        />
         <YAxis tick />
         <CartesianGrid stroke="#f5f5f5" />
         <Line type="monotone" dataKey="value" stroke="#000" yAxisId={0} />

+ 5 - 1
src/types.ts

@@ -5,6 +5,10 @@ export type Case = {
 
 export type Cases = Case[];
 
-export type Data = (Case & { regression: number })[];
+export type Data = {
+  date: number;
+  value: number;
+  regression?: number;
+}[];
 
 export type Country = 'uk';

+ 40 - 6
src/utils/regression.ts

@@ -1,6 +1,9 @@
 import differenceInDays from 'date-fns/differenceInDays';
 import { Cases, Data } from '../types';
 
+// Start the regression line after this many cases have been recorded in total
+const regressionStart = 50;
+
 const mean = (values: number[]): number =>
   values.reduce((last, value) => last + value, 0) / values.length;
 
@@ -9,11 +12,36 @@ export function getExponentialRegression(cases: Cases): Data {
     return [];
   }
 
+  const { index: startIndex } = cases.reduce(
+    ({ sum, index }, { value }, nextIndex) => {
+      if (sum >= regressionStart) {
+        return { sum, index };
+      }
+
+      const nextSum = sum + value;
+      if (nextSum < regressionStart) {
+        return { sum: nextSum, index };
+      }
+
+      return { sum: nextSum, index: nextIndex };
+    },
+    { sum: 0, index: -1 },
+  );
+
+  if (startIndex === -1) {
+    return cases.map(({ date, value }) => ({
+      date: date.getTime(),
+      value,
+    }));
+  }
+
+  const casesToRegress = cases.slice(startIndex);
+
   // It's assumed that the input here is ordered by date ascending
-  const minDate = cases[0].date;
+  const minDate = casesToRegress[0].date;
 
-  const xSeries = cases.map(({ date }) => 1 + differenceInDays(date, minDate));
-  const ySeries = cases.map(({ value }) => Math.log(value));
+  const xSeries = casesToRegress.map(({ date }) => 1 + differenceInDays(date, minDate));
+  const ySeries = casesToRegress.map(({ value }) => Math.log(value));
 
   const xBar = mean(xSeries);
   const yBar = mean(ySeries);
@@ -27,9 +55,15 @@ export function getExponentialRegression(cases: Cases): Data {
   const slope = covariance / xVariance;
   const intercept = yBar - slope * xBar;
 
-  return cases.map(({ date, value }, index) => ({
-    date,
+  const regressionAtDate = (date: Date): number => {
+    const xValue = differenceInDays(date, minDate);
+
+    return Math.exp(slope * xValue + intercept);
+  };
+
+  return cases.map(({ date, value }) => ({
+    date: date.getTime(),
     value,
-    regression: Math.exp(slope * xSeries[index] + intercept),
+    regression: regressionAtDate(date),
   }));
 }