diff --git a/common/stats.ts b/common/stats.ts index 258eec7c..9221e10e 100644 --- a/common/stats.ts +++ b/common/stats.ts @@ -1,11 +1,14 @@ export type Stats = { startDate: number dailyActiveUsers: number[] + dailyActiveUsersWeeklyAvg: number[] weeklyActiveUsers: number[] monthlyActiveUsers: number[] d1: number[] - d1Weekly: number[] - w1NewUsers: number[] + d1WeeklyAvg: number[] + nd1: number[] + nd1WeeklyAvg: number[] + nw1: number[] dailyBetCounts: number[] dailyContractCounts: number[] dailyCommentCounts: number[] diff --git a/functions/src/update-stats.ts b/functions/src/update-stats.ts index 046d0dc9..98b03931 100644 --- a/functions/src/update-stats.ts +++ b/functions/src/update-stats.ts @@ -12,6 +12,7 @@ import { Bet } from '../../common/bet' import { Contract } from '../../common/contract' import { Comment } from '../../common/comment' import { User } from '../../common/user' +import { Stats } from '../../common/stats' import { DAY_MS } from '../../common/util/time' import { average } from '../../common/util/math' @@ -109,7 +110,7 @@ export async function getDailyNewUsers( } export const updateStatsCore = async () => { - const today = dayjs().tz('America/Pacific').startOf('day').valueOf() + const today = dayjs().tz('America/Los_Angeles').startOf('day').valueOf() const startDate = today - numberOfDays * DAY_MS log('Fetching data for stats update...') @@ -145,6 +146,11 @@ export const updateStatsCore = async () => { ) const dailyActiveUsers = dailyUserIds.map((userIds) => userIds.length) + const dailyActiveUsersWeeklyAvg = dailyUserIds.map((_, i) => { + const start = Math.max(0, i - 6) + const end = i + 1 + return average(dailyActiveUsers.slice(start, end)) + }) const weeklyActiveUsers = dailyUserIds.map((_, i) => { const start = Math.max(0, i - 6) @@ -172,14 +178,31 @@ export const updateStatsCore = async () => { return retainedCount / uniques.size }) - const d1Weekly = d1.map((_, i) => { + const d1WeeklyAvg = d1.map((_, i) => { const start = Math.max(0, i - 6) const end = i + 1 return average(d1.slice(start, end)) }) const dailyNewUserIds = dailyNewUsers.map((users) => users.map((u) => u.id)) - const w1NewUsers = dailyNewUserIds.map((_userIds, i) => { + const nd1 = dailyUserIds.map((userIds, i) => { + if (i === 0) return 0 + + const uniques = new Set(userIds) + const yesterday = dailyNewUserIds[i - 1] + + const retainedCount = sumBy(yesterday, (userId) => + uniques.has(userId) ? 1 : 0 + ) + return retainedCount / uniques.size + }) + + const nd1WeeklyAvg = nd1.map((_, i) => { + const start = Math.max(0, i - 6) + const end = i + 1 + return average(nd1.slice(start, end)) + }) + const nw1 = dailyNewUserIds.map((_userIds, i) => { if (i < 13) return 0 const twoWeeksAgo = { @@ -243,6 +266,7 @@ export const updateStatsCore = async () => { const retainedCount = sumBy(Array.from(activeTwoMonthsAgo), (userId) => activeLastMonth.has(userId) ? 1 : 0 ) + if (activeTwoMonthsAgo.size === 0) return 0 return retainedCount / activeTwoMonthsAgo.size }) @@ -320,14 +344,17 @@ export const updateStatsCore = async () => { return total }) - const statsData = { + const statsData: Stats = { startDate: startDate.valueOf(), dailyActiveUsers, + dailyActiveUsersWeeklyAvg, weeklyActiveUsers, monthlyActiveUsers, d1, - d1Weekly, - w1NewUsers, + d1WeeklyAvg, + nd1, + nd1WeeklyAvg, + nw1, dailyBetCounts, dailyContractCounts, dailyCommentCounts, diff --git a/web/components/analytics/charts.tsx b/web/components/analytics/charts.tsx index e078ce21..131ce2a0 100644 --- a/web/components/analytics/charts.tsx +++ b/web/components/analytics/charts.tsx @@ -64,18 +64,21 @@ export function DailyPercentChart(props: { startDate: number dailyPercent: number[] small?: boolean + excludeFirstDays?: number }) { - const { dailyPercent, startDate, small } = props + const { dailyPercent, startDate, small, excludeFirstDays } = props const { width } = useWindowSize() const dates = dailyPercent.map((_, i) => dayjs(startDate).add(i, 'day').toDate() ) - const points = zip(dates, dailyPercent).map(([date, percent]) => ({ - x: date, - y: percent, - })) + const points = zip(dates, dailyPercent) + .map(([date, percent]) => ({ + x: date, + y: percent, + })) + .slice(excludeFirstDays ?? 0) const data = [{ id: 'Percent', data: points, color: '#11b981' }] const bottomAxisTicks = width && width < 600 ? 6 : undefined @@ -128,7 +131,7 @@ function Tooltip(props: { point: Point; isPercent?: boolean }) { }} > {point.serieId}{' '} - {isPercent ? formatPercent(+point.data.y) : point.data.yFormatted} + {isPercent ? formatPercent(+point.data.y) : Math.round(+point.data.y)}
{dayjs(point.data.x).format('MMM DD')}
diff --git a/web/pages/stats.tsx b/web/pages/stats.tsx index 5328e5a2..67bcbb56 100644 --- a/web/pages/stats.tsx +++ b/web/pages/stats.tsx @@ -49,16 +49,19 @@ export default function Analytics() { export function CustomAnalytics(props: Stats) { const { startDate, - d1, - d1Weekly, - w1NewUsers, dailyActiveUsers, + dailyActiveUsersWeeklyAvg, + weeklyActiveUsers, + monthlyActiveUsers, + d1, + d1WeeklyAvg, + nd1, + nd1WeeklyAvg, + nw1, dailyBetCounts, dailyContractCounts, dailyCommentCounts, dailySignups, - weeklyActiveUsers, - monthlyActiveUsers, weekOnWeekRetention, monthlyRetention, weeklyActivationRate, @@ -108,6 +111,16 @@ export function CustomAnalytics(props: Stats) { /> ), }, + { + title: 'Daily (7d avg)', + content: ( + + ), + }, { title: 'Weekly', content: ( @@ -132,13 +145,11 @@ export function CustomAnalytics(props: Stats) { /> - + <Title text="Retention" /> <p className="text-gray-500"> - The fraction of users that took an action yesterday that took an action - today. + What fraction of active users are still active after the given time + period? </p> - <Spacer h={4} /> - <Tabs defaultIndex={1} tabs={[ @@ -149,39 +160,85 @@ export function CustomAnalytics(props: Stats) { dailyPercent={d1} startDate={startDate} small + excludeFirstDays={1} /> ), }, { - title: 'D1 weekly average', + title: 'D1 (7d avg)', content: ( <DailyPercentChart - dailyPercent={d1Weekly} + dailyPercent={d1WeeklyAvg} startDate={startDate} small + excludeFirstDays={7} + /> + ), + }, + { + title: 'W1', + content: ( + <DailyPercentChart + dailyPercent={weekOnWeekRetention} + startDate={oneWeekLaterDate} + small + excludeFirstDays={14} + /> + ), + }, + { + title: 'M1', + content: ( + <DailyPercentChart + dailyPercent={monthlyRetention} + startDate={oneWeekLaterDate} + small + excludeFirstDays={60} /> ), }, ]} /> - <Spacer h={8} /> - <Title text="W1 New users" /> + <Spacer h={8} /> + <Title text="New user retention" /> <p className="text-gray-500"> - The fraction of new users two weeks ago that took an action in the past - week. + What fraction of new users are still active after the given time period? </p> <Spacer h={4} /> <Tabs - defaultIndex={0} + defaultIndex={2} tabs={[ { - title: 'W1', + title: 'ND1', content: ( <DailyPercentChart - dailyPercent={w1NewUsers} + dailyPercent={nd1} startDate={startDate} + excludeFirstDays={1} + small + /> + ), + }, + { + title: 'ND1 (7d avg)', + content: ( + <DailyPercentChart + dailyPercent={nd1WeeklyAvg} + startDate={startDate} + excludeFirstDays={7} + small + /> + ), + }, + { + title: 'NW1', + content: ( + <DailyPercentChart + dailyPercent={nw1} + startDate={startDate} + excludeFirstDays={14} small /> ), @@ -239,38 +296,6 @@ export function CustomAnalytics(props: Stats) { <Spacer h={8} /> - <Title text="Retention" /> - <p className="text-gray-500"> - What fraction of active users are still active after the given time - period? - </p> - <Tabs - defaultIndex={0} - tabs={[ - { - title: 'Weekly', - content: ( - <DailyPercentChart - dailyPercent={weekOnWeekRetention.slice(7)} - startDate={oneWeekLaterDate} - small - /> - ), - }, - { - title: 'Monthly', - content: ( - <DailyPercentChart - dailyPercent={monthlyRetention.slice(7)} - startDate={oneWeekLaterDate} - small - /> - ), - }, - ]} - /> - <Spacer h={8} /> - <Title text="Weekly activation rate" /> <p className="text-gray-500"> Out of all new users this week, how many placed at least one bet? @@ -293,6 +318,7 @@ export function CustomAnalytics(props: Stats) { dailyPercent={dailyDividedByWeekly} startDate={oneWeekLaterDate} small + excludeFirstDays={7} /> ), }, @@ -303,6 +329,7 @@ export function CustomAnalytics(props: Stats) { dailyPercent={dailyDividedByMonthly} startDate={oneWeekLaterDate} small + excludeFirstDays={30} /> ), }, @@ -313,6 +340,7 @@ export function CustomAnalytics(props: Stats) { dailyPercent={weeklyDividedByMonthly} startDate={oneWeekLaterDate} small + excludeFirstDays={30} /> ), },