Add more stats. Fix timezone. Group retention and new user retention
This commit is contained in:
parent
d0973de2b4
commit
4dc3eada1f
|
@ -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[]
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user