فهرست منبع

Optional regression buffer

Fela Maslen 5 سال پیش
والد
کامیت
e8fe8a0c69
2فایلهای تغییر یافته به همراه48 افزوده شده و 22 حذف شده
  1. 20 4
      src/components/graph-cases.tsx
  2. 28 18
      src/utils/regression.ts

+ 20 - 4
src/components/graph-cases.tsx

@@ -24,6 +24,11 @@ type Props = {
 
 const GraphCases: React.FC<Props> = ({ countries }) => {
   const [showCumulative, setCumulative] = React.useState<boolean>(true);
+  const [regressionBuffer, setRegressionBuffer] = React.useState<number>(0);
+  const onChangeRegressionBuffer = React.useCallback(
+    event => setRegressionBuffer(Number(event.target.value)),
+    [],
+  );
 
   const countryCases = React.useMemo<CountryCases>(
     () =>
@@ -34,10 +39,10 @@ const GraphCases: React.FC<Props> = ({ countries }) => {
     [countries],
   );
 
-  const data = React.useMemo<Data>(() => processCases(countryCases, showCumulative), [
-    countryCases,
-    showCumulative,
-  ]);
+  const data = React.useMemo<Data>(
+    () => processCases(countryCases, showCumulative, regressionBuffer),
+    [countryCases, showCumulative, regressionBuffer],
+  );
 
   if (!data.length) {
     return (
@@ -55,6 +60,17 @@ const GraphCases: React.FC<Props> = ({ countries }) => {
         <input type="radio" onChange={(): void => setCumulative(false)} checked={!showCumulative} />
         Daily
       </p>
+      <p>
+        Regression buffer:{' '}
+        <input
+          type="range"
+          min={0}
+          max={5}
+          step="1"
+          value={regressionBuffer}
+          onChange={onChangeRegressionBuffer}
+        />
+      </p>
       <LineChart width={640} height={480} data={data} margin={margin}>
         <XAxis
           dataKey="xValue"

+ 28 - 18
src/utils/regression.ts

@@ -9,7 +9,7 @@ import { Cases, Data, DataPoint, CountryCase, CountryCases, CountryDataPoint } f
 const regressionStart = 50;
 
 // Number of days to extrapolate the regression fit on the graph
-const futureDays = 10;
+const futureDays = 3;
 
 const mean = (values: number[]): number =>
   values.reduce((last, value) => last + value, 0) / values.length;
@@ -54,17 +54,17 @@ const withCumulative = (cumulative: boolean) => (cases: WithNumericDate): WithCu
 
 const logArray = (values: number[]): number[] => values.map(value => Math.log(value));
 
-const withExponentialRegression = (cumulative: boolean) => (
+const withExponentialRegression = (cumulative: boolean, regressionBuffer: number) => (
   cases: WithCumulative,
 ): CountryDataPoint[] => {
   const startIndex = cases.findIndex(
     ({ valueCumulative = 0 }) => valueCumulative >= regressionStart,
   );
-  if (startIndex === -1) {
+  if (startIndex === -1 || startIndex >= cases.length - regressionBuffer) {
     return cases;
   }
 
-  const casesToRegress = cases.slice(startIndex);
+  const casesToRegress = cases.slice(startIndex, cases.length - regressionBuffer);
 
   // It's assumed that the input here is ordered by date ascending
   const minDate: Date = new Date(casesToRegress[0].date);
@@ -103,13 +103,17 @@ const withExponentialRegression = (cumulative: boolean) => (
   }));
 };
 
-function processCountryCases(cases: Cases, cumulative: boolean): CountryDataPoint[] {
+function processCountryCases(
+  cases: Cases,
+  cumulative: boolean,
+  regressionBuffer: number,
+): CountryDataPoint[] {
   if (!cases.length) {
     return [];
   }
 
   return compose<Cases, WithNumericDate, WithCumulative, CountryDataPoint[]>(
-    withExponentialRegression(cumulative),
+    withExponentialRegression(cumulative, regressionBuffer),
     withCumulative(cumulative),
     withNumericDate,
   )(cases);
@@ -159,7 +163,7 @@ function fillData(countryCases: CountryCases): CountryCases {
       country,
       dataSource: {
         ...dataSource,
-        cases: times.reduce((last: Cases, time: number, index: number): Cases => {
+        cases: times.reduce((last: Cases, time: number): Cases => {
           const matchingCase = dataSource.cases.find(({ date }) => date.getTime() === time);
           if (matchingCase) {
             return [...last, matchingCase];
@@ -178,20 +182,26 @@ function fillData(countryCases: CountryCases): CountryCases {
   );
 }
 
-export function processCases(countryCases: CountryCases, cumulative = true): Data {
+export function processCases(
+  countryCases: CountryCases,
+  cumulative = true,
+  regressionBuffer = 0, // Don't count the last X days into the regression fit
+): Data {
   const filledData = fillData(countryCases);
 
   const data = filledData.map(({ country, dataSource: { cases } }) =>
-    processCountryCases(cases, cumulative).map(({ date, xValue, value, regression }) => ({
-      date,
-      xValue,
-      value: {
-        [country]: value,
-      },
-      regression: {
-        [country]: regression,
-      },
-    })),
+    processCountryCases(cases, cumulative, regressionBuffer).map(
+      ({ date, xValue, value, regression }) => ({
+        date,
+        xValue,
+        value: {
+          [country]: value,
+        },
+        regression: {
+          [country]: regression,
+        },
+      }),
+    ),
   );
 
   return combineData(data);