Add more stats. Fix timezone. Group retention and new user retention

This commit is contained in:
James Grugett 2022-09-19 18:41:24 -05:00
parent d0973de2b4
commit 4dc3eada1f
4 changed files with 126 additions and 65 deletions

View File

@ -1,11 +1,14 @@
export type Stats = { export type Stats = {
startDate: number startDate: number
dailyActiveUsers: number[] dailyActiveUsers: number[]
dailyActiveUsersWeeklyAvg: number[]
weeklyActiveUsers: number[] weeklyActiveUsers: number[]
monthlyActiveUsers: number[] monthlyActiveUsers: number[]
d1: number[] d1: number[]
d1Weekly: number[] d1WeeklyAvg: number[]
w1NewUsers: number[] nd1: number[]
nd1WeeklyAvg: number[]
nw1: number[]
dailyBetCounts: number[] dailyBetCounts: number[]
dailyContractCounts: number[] dailyContractCounts: number[]
dailyCommentCounts: number[] dailyCommentCounts: number[]

View File

@ -12,6 +12,7 @@ import { Bet } from '../../common/bet'
import { Contract } from '../../common/contract' import { Contract } from '../../common/contract'
import { Comment } from '../../common/comment' import { Comment } from '../../common/comment'
import { User } from '../../common/user' import { User } from '../../common/user'
import { Stats } from '../../common/stats'
import { DAY_MS } from '../../common/util/time' import { DAY_MS } from '../../common/util/time'
import { average } from '../../common/util/math' import { average } from '../../common/util/math'
@ -109,7 +110,7 @@ export async function getDailyNewUsers(
} }
export const updateStatsCore = async () => { 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 const startDate = today - numberOfDays * DAY_MS
log('Fetching data for stats update...') log('Fetching data for stats update...')
@ -145,6 +146,11 @@ export const updateStatsCore = async () => {
) )
const dailyActiveUsers = dailyUserIds.map((userIds) => userIds.length) 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 weeklyActiveUsers = dailyUserIds.map((_, i) => {
const start = Math.max(0, i - 6) const start = Math.max(0, i - 6)
@ -172,14 +178,31 @@ export const updateStatsCore = async () => {
return retainedCount / uniques.size return retainedCount / uniques.size
}) })
const d1Weekly = d1.map((_, i) => { const d1WeeklyAvg = d1.map((_, i) => {
const start = Math.max(0, i - 6) const start = Math.max(0, i - 6)
const end = i + 1 const end = i + 1
return average(d1.slice(start, end)) return average(d1.slice(start, end))
}) })
const dailyNewUserIds = dailyNewUsers.map((users) => users.map((u) => u.id)) 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 if (i < 13) return 0
const twoWeeksAgo = { const twoWeeksAgo = {
@ -243,6 +266,7 @@ export const updateStatsCore = async () => {
const retainedCount = sumBy(Array.from(activeTwoMonthsAgo), (userId) => const retainedCount = sumBy(Array.from(activeTwoMonthsAgo), (userId) =>
activeLastMonth.has(userId) ? 1 : 0 activeLastMonth.has(userId) ? 1 : 0
) )
if (activeTwoMonthsAgo.size === 0) return 0
return retainedCount / activeTwoMonthsAgo.size return retainedCount / activeTwoMonthsAgo.size
}) })
@ -320,14 +344,17 @@ export const updateStatsCore = async () => {
return total return total
}) })
const statsData = { const statsData: Stats = {
startDate: startDate.valueOf(), startDate: startDate.valueOf(),
dailyActiveUsers, dailyActiveUsers,
dailyActiveUsersWeeklyAvg,
weeklyActiveUsers, weeklyActiveUsers,
monthlyActiveUsers, monthlyActiveUsers,
d1, d1,
d1Weekly, d1WeeklyAvg,
w1NewUsers, nd1,
nd1WeeklyAvg,
nw1,
dailyBetCounts, dailyBetCounts,
dailyContractCounts, dailyContractCounts,
dailyCommentCounts, dailyCommentCounts,

View File

@ -64,18 +64,21 @@ export function DailyPercentChart(props: {
startDate: number startDate: number
dailyPercent: number[] dailyPercent: number[]
small?: boolean small?: boolean
excludeFirstDays?: number
}) { }) {
const { dailyPercent, startDate, small } = props const { dailyPercent, startDate, small, excludeFirstDays } = props
const { width } = useWindowSize() const { width } = useWindowSize()
const dates = dailyPercent.map((_, i) => const dates = dailyPercent.map((_, i) =>
dayjs(startDate).add(i, 'day').toDate() dayjs(startDate).add(i, 'day').toDate()
) )
const points = zip(dates, dailyPercent).map(([date, percent]) => ({ const points = zip(dates, dailyPercent)
x: date, .map(([date, percent]) => ({
y: percent, x: date,
})) y: percent,
}))
.slice(excludeFirstDays ?? 0)
const data = [{ id: 'Percent', data: points, color: '#11b981' }] const data = [{ id: 'Percent', data: points, color: '#11b981' }]
const bottomAxisTicks = width && width < 600 ? 6 : undefined const bottomAxisTicks = width && width < 600 ? 6 : undefined
@ -128,7 +131,7 @@ function Tooltip(props: { point: Point; isPercent?: boolean }) {
}} }}
> >
<strong>{point.serieId}</strong>{' '} <strong>{point.serieId}</strong>{' '}
{isPercent ? formatPercent(+point.data.y) : point.data.yFormatted} {isPercent ? formatPercent(+point.data.y) : Math.round(+point.data.y)}
</div> </div>
<div>{dayjs(point.data.x).format('MMM DD')}</div> <div>{dayjs(point.data.x).format('MMM DD')}</div>
</Col> </Col>

View File

@ -49,16 +49,19 @@ export default function Analytics() {
export function CustomAnalytics(props: Stats) { export function CustomAnalytics(props: Stats) {
const { const {
startDate, startDate,
d1,
d1Weekly,
w1NewUsers,
dailyActiveUsers, dailyActiveUsers,
dailyActiveUsersWeeklyAvg,
weeklyActiveUsers,
monthlyActiveUsers,
d1,
d1WeeklyAvg,
nd1,
nd1WeeklyAvg,
nw1,
dailyBetCounts, dailyBetCounts,
dailyContractCounts, dailyContractCounts,
dailyCommentCounts, dailyCommentCounts,
dailySignups, dailySignups,
weeklyActiveUsers,
monthlyActiveUsers,
weekOnWeekRetention, weekOnWeekRetention,
monthlyRetention, monthlyRetention,
weeklyActivationRate, weeklyActivationRate,
@ -108,6 +111,16 @@ export function CustomAnalytics(props: Stats) {
/> />
), ),
}, },
{
title: 'Daily (7d avg)',
content: (
<DailyCountChart
dailyCounts={dailyActiveUsersWeeklyAvg}
startDate={startDate}
small
/>
),
},
{ {
title: 'Weekly', title: 'Weekly',
content: ( content: (
@ -132,13 +145,11 @@ export function CustomAnalytics(props: Stats) {
/> />
<Spacer h={8} /> <Spacer h={8} />
<Title text="D1" /> <Title text="Retention" />
<p className="text-gray-500"> <p className="text-gray-500">
The fraction of users that took an action yesterday that took an action What fraction of active users are still active after the given time
today. period?
</p> </p>
<Spacer h={4} />
<Tabs <Tabs
defaultIndex={1} defaultIndex={1}
tabs={[ tabs={[
@ -149,39 +160,85 @@ export function CustomAnalytics(props: Stats) {
dailyPercent={d1} dailyPercent={d1}
startDate={startDate} startDate={startDate}
small small
excludeFirstDays={1}
/> />
), ),
}, },
{ {
title: 'D1 weekly average', title: 'D1 (7d avg)',
content: ( content: (
<DailyPercentChart <DailyPercentChart
dailyPercent={d1Weekly} dailyPercent={d1WeeklyAvg}
startDate={startDate} startDate={startDate}
small 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"> <p className="text-gray-500">
The fraction of new users two weeks ago that took an action in the past What fraction of new users are still active after the given time period?
week.
</p> </p>
<Spacer h={4} /> <Spacer h={4} />
<Tabs <Tabs
defaultIndex={0} defaultIndex={2}
tabs={[ tabs={[
{ {
title: 'W1', title: 'ND1',
content: ( content: (
<DailyPercentChart <DailyPercentChart
dailyPercent={w1NewUsers} dailyPercent={nd1}
startDate={startDate} 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 small
/> />
), ),
@ -239,38 +296,6 @@ export function CustomAnalytics(props: Stats) {
<Spacer h={8} /> <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" /> <Title text="Weekly activation rate" />
<p className="text-gray-500"> <p className="text-gray-500">
Out of all new users this week, how many placed at least one bet? 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} dailyPercent={dailyDividedByWeekly}
startDate={oneWeekLaterDate} startDate={oneWeekLaterDate}
small small
excludeFirstDays={7}
/> />
), ),
}, },
@ -303,6 +329,7 @@ export function CustomAnalytics(props: Stats) {
dailyPercent={dailyDividedByMonthly} dailyPercent={dailyDividedByMonthly}
startDate={oneWeekLaterDate} startDate={oneWeekLaterDate}
small small
excludeFirstDays={30}
/> />
), ),
}, },
@ -313,6 +340,7 @@ export function CustomAnalytics(props: Stats) {
dailyPercent={weeklyDividedByMonthly} dailyPercent={weeklyDividedByMonthly}
startDate={oneWeekLaterDate} startDate={oneWeekLaterDate}
small small
excludeFirstDays={30}
/> />
), ),
}, },