library(tidyverse)
library(readr)
library(ggplot2)
library(tibble)
library(lubridate)
library(janitor)
library(scales)
library(knitr)
library(dplyr)
Sys.setlocale("LC_TIME", "C") # نام روزهای هفته به انگلیسی، مستقل از تنظیمات سیستم
## [1] "C"
دو ماه داده ردیاب فیتبیت از 35 کاربر نشان میدهد که بازار سلامت و تندرستی عادت اشتباهی را میفروشد. قویترین رابطه در این دادهها میان قدم و سلامت نیست، بلکه میان بیتحرکی و خواب بد است.
tibble(
`شاخص` = c("کاربران تحلیلشده", "روزهای دارای فعالیت ثبتشده", "شبهای دارای داده خواب",
"میانه نرخ استفاده از دستگاه", "میانگین زمان بیتحرکی در روز",
"شبهای کمتر از ۷ ساعت خواب", "روزهای رسیدن به ۱۰٬۰۰۰ قدم",
"همبستگی: زمان بیتحرکی و خواب"),
`مقدار` = c(
as.character(n_users),
comma(nrow(worn)),
comma(nrow(sleep)),
percent(median((worn |> count(id, name = "d") |>
left_join(daily |> distinct(id, period) |>
left_join(daily |> group_by(period) |>
summarise(w = as.numeric(max(date) - min(date)) + 1, .groups = "drop"),
by = "period") |> group_by(id) |>
summarise(w = sum(w), .groups = "drop"), by = "id") |>
mutate(r = d / w))$r), 1),
paste0(round(mean(worn$sedentary_minutes) / 60, 1), " ساعت"),
percent(mean(sleep$hours_asleep < 7), 0.1),
percent(mean(worn$total_steps >= 10000), 0.1),
round(cor((worn |> inner_join(sleep, by = c("id", "date")))$sedentary_minutes,
(worn |> inner_join(sleep, by = c("id", "date")))$hours_asleep), 2)
)
) |> kable(caption = "شاخصهای کلیدی")
| شاخص | مقدار |
|---|---|
| کاربران تحلیلشده | 35 |
| روزهای دارای فعالیت ثبتشده | 1,235 |
| شبهای دارای داده خواب | 882 |
| میانه نرخ استفاده از دستگاه | 63% |
| میانگین زمان بیتحرکی در روز | 15.9 ساعت |
| شبهای کمتر از ۷ ساعت خواب | 49.1% |
| روزهای رسیدن به ۱۰٬۰۰۰ قدم | 34.0% |
| همبستگی: زمان بیتحرکی و خواب | -0.48 |
سه توصیه اصلی: هدف ثابت ۱۰٬۰۰۰ قدم با هدفی سازگارشونده جایگزین شود؛ اپلیکیشن بلابیت بر پایه پیوند «کمتر بنشین، بهتر بخواب» بازاریابی شود؛ و اعلانها روی همان دو ساعتی تنظیم شوند که کاربران از پیش در حرکتاند.
خروجی ۱: خلاصهای روشن از وظیفه کسبوکار
بلابیت سازنده محصولات فناورانه با محوریت سلامت برای زنان است که در سال ۲۰۱۳ توسط اورشکا سرشن و ساندو مور بنیان گذاشته شد. خط محصول شامل اپلیکیشن بلابیت، ردیاب Leaf، ساعت Time، بطری آب Spring و عضویت اشتراکی است.
اورشکا سرشن باور دارد که تحلیل دادههای تناسب اندام دستگاههای هوشمند میتواند مسیرهای تازهای برای رشد بگشاید. او از تیم تحلیل بازاریابی خواسته است دادههای استفاده از دستگاههای هوشمند غیر بلابیت را تحلیل کند، یک محصول بلابیت را انتخاب کند و توصیههایی در سطح کلان برای راهبرد بازاریابی ارائه دهد.
مشخص کردن اینکه مصرفکنندگان در عمل چگونه از دستگاههای هوشمند غیر بلابیت استفاده میکنند، و تعیین اینکه بلابیت کدام یک از شکافهای رفتاری حاصل را میتواند از راه بازاریابی اپلیکیشن خود پر کند.
۱. چه روندهایی در استفاده از دستگاههای هوشمند دیده میشود؟ ۲. این روندها چگونه بر مشتریان بلابیت قابل تعمیم است؟ ۳. این روندها چگونه میتواند بر راهبرد بازاریابی بلابیت اثر بگذارد؟
| ذینفع | نقش | آنچه از این گزارش نیاز دارد |
|---|---|---|
| اورشکا سرشن | همبنیانگذار، مدیر ارشد خلاقیت | فرصتهای رشد مبتنی بر شواهد |
| ساندو مور | همبنیانگذار، هیئت اجرایی | نتیجهگیریهای تحلیلی معتبر |
| تیم تحلیل بازاریابی | همکاران | مبنایی تکرارپذیر برای تصمیمهای کمپین |
اپلیکیشن بلابیت. این اپلیکیشن همه دستگاههای خط محصول را به هم وصل میکند، همان سطحی است که انگیزههای رفتاری از آن ارائه میشود، و اشتراک عضویت را پیش میبرد. بنابراین یافتههای رفتاری در همینجا سریعتر و کمهزینهتر از هر جای دیگر قابل اجراست.
خروجی ۲: توصیف همه منابع داده بهکاررفته
| مورد | توضیح |
|---|---|
| مجموعه داده | FitBit Fitness Tracker Data |
| منتشرشده توسط | Mobius، از طریق Kaggle |
| مجوز | CC0، مالکیت عمومی، بدون محدودیت استفاده |
| گردآوری | نظرسنجی Amazon Mechanical Turk، ۱۲ مارس تا ۱۲ مه ۲۰۱۶ |
| رضایت | شرکتکنندگان صراحتاً به ارسال دادههای ردیاب شخصی خود رضایت دادهاند |
| حریم خصوصی | بدون نام، نشانی یا اطلاعات جمعیتی؛ کاربران فقط با شناسه عددی مشخص شدهاند |
| ذخیرهسازی | بهصورت محلی در زیرپوشههای تاریخدار نگهداری شد؛ فایلهای اصلی تغییر نکردند |
از آنجا که دادهها تحت مجوز CC0 هستند و هیچ شناسه شخصی ندارند، هیچ
مانع حقوقی، حریم خصوصی یا امنیتی برای این تحلیل وجود ندارد. همه پاکسازی
روی نسخههای کپی انجام شد و فایلهای .csv اصلی هرگز بازنویسی
نشدند.
مجموعه داده در قالب دو پوشه خروجی برای دو ماه پیاپی ارائه میشود:
mturkfitbit_export_3.12.16-4.11.16، ۱۲ مارس تا ۱۱ آوریل
۲۰۱۶mturkfitbit_export_4.12.16-5.12.16، ۱۲ آوریل تا ۱۲ مه
۲۰۱۶هر دو پوشه همان اندازهگیریها را در چهار سطح جزئیات دارند: روزانه،
ساعتی، دقیقهای و ثانیهای، در قالب بلند (Narrow) و عریض.
بیشتر تحلیلهای منتشرشده از این مجموعه داده تنها از پوشه دوم
استفاده میکنند. هر دو پوشه در فایل
dailyActivity_merged.csv ساختار یکسانی دارند، بنابراین این
تحلیل هر دو را با هم ترکیب میکند:
tibble(
`دامنه` = c("تحلیل رایج با یک پوشه", "این تحلیل (هر دو پوشه)"),
`کاربران` = c(33, n_users),
`روزهای پوششدادهشده` = c(31, as.numeric(max(daily$date) - min(daily$date)) + 1),
`شبهای دارای داده خواب` = c(410, nrow(sleep))
) |> kable(caption = "ترکیب هر دو پوشه خروجی، پایه شواهد را تقریباً دو برابر میکند")
| دامنه | کاربران | روزهای پوششدادهشده | شبهای دارای داده خواب |
|---|---|---|---|
| تحلیل رایج با یک پوشه | 33 | 31 | 410 |
| این تحلیل (هر دو پوشه) | 35 | 62 | 882 |
از ۱۸ فایل موجود، شش فایل بار تحلیل را بر دوش میکشند. فایلهای
dailySteps_merged.csv،
dailyCalories_merged.csv و
dailyIntensities_merged.csv کنار گذاشته شدند، چون ستونهایی
را تکرار میکنند که پیشتر در dailyActivity_merged.csv وجود
دارد. این موضوع بررسی شد، نه فرض:
steps_only <- read_csv(file.path(P2, "dailySteps_merged.csv"), show_col_types = FALSE)
activity <- read_csv(file.path(P2, "dailyActivity_merged.csv"), show_col_types = FALSE)
check <- steps_only |> rename(day = ActivityDay) |>
inner_join(activity |> rename(day = ActivityDate), by = c("Id", "day"))
cat("روزهای کاربری مقایسهشده:", nrow(check), "\n")
## روزهای کاربری مقایسهشده: 578
cat("شمار قدمها در همه سطرها یکسان است:", all(check$StepTotal == check$TotalSteps), "\n")
## شمار قدمها در همه سطرها یکسان است: TRUE
| فایل استفادهشده | کاربرد |
|---|---|
dailyActivity_merged |
قدم، مسافت، دقایق شدت فعالیت و کالری در هر روز |
hourlySteps_merged |
الگوی فعالیت در ساعتهای شبانهروز |
sleepDay_merged |
مجموع خواب شبانه، دوره آوریل |
minuteSleep_merged |
تجمیعشده به شب، برای گسترش پوشش خواب به مارس |
weightLogInfo_merged |
میزان استفاده از قابلیتها و رفتار ثبت دستی |
فایل heartrate_seconds_merged.csv کنار گذاشته شد: ۸۶
مگابایت حجم برای تنها ۱۴ کاربر از 35 کاربر، که زیرنمونهای بسیار کوچک
برای پشتیبانی از یک یافته است. فایلهای فعالیت دقیقهای نیز به دلیل
همپوشانی با تجمیعهای ساعتی و روزانه کنار گذاشته شدند.
tibble(
`معیار` = c("Reliable (قابل اتکا)", "Original (اصیل)", "Comprehensive (جامع)",
"Current (بهروز)", "Cited (مستند)"),
`ارزیابی` = c("ضعیف", "ضعیف", "ضعیف", "نامناسب", "خوب"),
`توضیح` = c(
paste0(n_users, " شرکتکننده خودانتخاب؛ بدون راستیآزمایی مستقل"),
"بازنشر توسط شخص ثالث، نه گردآوریشده توسط فیتبیت یا بلابیت",
"نه سن، نه جنسیت، نه قد، نه موقعیت مکانی و نه وضعیت پایه سلامت ثبت شده است",
"گردآوری در ۲۰۱۶؛ سختافزار پوشیدنی و انتظار کاربران از آن زمان تغییر کرده است",
"بهروشنی مستند و با مجوز آزاد CC0 منتشر شده است"
)
) |> kable(caption = "ارزیابی ROCCC برای مجموعه داده FitBit Fitness Tracker")
| معیار | ارزیابی | توضیح |
|---|---|---|
| Reliable (قابل اتکا) | ضعیف | 35 شرکتکننده خودانتخاب؛ بدون راستیآزمایی مستقل |
| Original (اصیل) | ضعیف | بازنشر توسط شخص ثالث، نه گردآوریشده توسط فیتبیت یا بلابیت |
| Comprehensive (جامع) | ضعیف | نه سن، نه جنسیت، نه قد، نه موقعیت مکانی و نه وضعیت پایه سلامت ثبت شده است |
| Current (بهروز) | نامناسب | گردآوری در ۲۰۱۶؛ سختافزار پوشیدنی و انتظار کاربران از آن زمان تغییر کرده است |
| Cited (مستند) | خوب | بهروشنی مستند و با مجوز آزاد CC0 منتشر شده است |
این مجموعه داده معیار ROCCC را خوب برآورده نمیکند، و این نکته پیش از ارائه یافتهها بیان میشود، نه پس از آن. نمونه کوچک و خودانتخاب است و فیلد جنسیت ندارد؛ این محدودیتی جدی است وقتی تحلیل برای شرکتی انجام میشود که منحصراً به زنان میفروشد. هر یافته زیر یک نشانه جهتدار است که باید در برابر دادههای خود بلابیت راستیآزمایی شود، نه نتیجهای که بتوان بودجهای بر آن بست.
سرشن پیشنهاد کرده بود مجموعه داده دیگری برای رفع این محدودیتها در نظر گرفته شود. ارزشمندترین افزوده، دادههای تلهمتری خود اپلیکیشن بلابیت است، جایی که جنسیت، سن و خط محصول مشخص است.
خروجی ۳: مستندسازی هرگونه پاکسازی یا دستکاری داده
R به همراه tidyverse، به سه دلیل: مجموعه داده در سطح دقیقه از محدوده کار راحت اکسل فراتر میرود (بیش از ۱٫۳ میلیون سطر)؛ هر گام پاکسازی به شکل کد ثبت میشود و در نتیجه تکرارپذیر و قابل بازبینی است؛ و یک محیط واحد پاکسازی، آمار و تصویرسازی را بدون نیاز به انتقال میان ابزارها انجام میدهد. این سند با R Markdown نوشته شده است، بنابراین هر عددی که نقل میشود هنگام تولید سند محاسبه میشود و دستی تایپ نشده است.
tibble(
`بررسی` = c("سطرهای تکراری در فعالیت روزانه",
"سطرهای تکراری در خواب (جمعبندی آوریل)",
"مقادیر جاافتاده در فعالیت روزانه",
"روزهای با صفر قدم ثبتشده",
"ثبتهای وزن بدون مقدار چربی بدن"),
`نتیجه` = c(
sum(duplicated(daily_raw)),
sum(duplicated(sleep_apr_raw)),
sum(is.na(daily_raw)),
sum(!daily$is_wear_day),
sum(is.na(weight$fat))
)
) |> kable(caption = "بررسیهای درستی داده پیش از هر تحلیل")
| بررسی | نتیجه |
|---|---|
| سطرهای تکراری در فعالیت روزانه | 0 |
| سطرهای تکراری در خواب (جمعبندی آوریل) | 3 |
| مقادیر جاافتاده در فعالیت روزانه | 22867493 |
| روزهای با صفر قدم ثبتشده | 138 |
| ثبتهای وزن بدون مقدار چربی بدن | 94 |
۱. ترکیب هر دو پوشه خروجی برای فعالیت روزانه، قدمهای
ساعتی و وزن، پس از تأیید یکسان بودن ساختارها. ستون period
نگه میدارد که هر سطر از کدام دوره آمده است. ۲. یکسانسازی نام
ستونها به شکل snake_case با
janitor::clean_names(). ۳. تبدیل تاریخ از
متن. تابع read_csv() مقدار
"4/12/2016" را به صورت رشته متنی وارد میکند؛
lubridate::mdy() و mdy_hms() آن را به شیء
تاریخ تبدیل میکنند تا بتوان مرتب کرد، فیلتر گرفت و بر پایه روز هفته
گروهبندی کرد. ۴. حذف رکوردهای تکراری. فایل
sleepDay_merged.csv سه سطر کاملاً یکسان دارد که نقصی مستند
در این مجموعه داده است. حذف تکرار بر پایه id و
date روی هر جدول اعمال شد، به عنوان محافظتی در مرز میان دو
پوشه. ۵. جداسازی روزهای بدون استفاده از دستگاه. ۶.
تجمیع داده خواب دقیقهای مارس به مجموع شبانه. ۷.
فیلدهای مشتقشده: روز هفته، مجموع دقایق فعال، ساعتهای
خواب، دقایق بیداری در رختخواب، بازدهی خواب و نرخ استفاده از دستگاه به
تفکیک کاربر.
138 سطر صفر قدم ثبت کردهاند، در کنار چیزی نزدیک به یک روز کامل دقایق بیتحرکی. اینها نشانه دستگاهی هستند که روی شارژ رها شده، نه شرکتکنندهای که ۲۴ ساعت بیحرکت مانده باشد:
daily |>
group_by(`نوع روز` = ifelse(is_wear_day, "دستگاه استفاده شده", "صفر قدم ثبت شده")) |>
summarise(`روزها` = n(),
`میانگین دقایق بیتحرکی` = round(mean(sedentary_minutes)),
`میانگین کالری` = round(mean(calories)), .groups = "drop") |>
kable(caption = "روزهای صفر قدم بهطور میانگین نزدیک به ۱۴۴۰ دقیقه بیتحرکی دارند، که تأیید میکند دستگاه استفاده نشده است")
| نوع روز | روزها | میانگین دقایق بیتحرکی | میانگین کالری |
|---|---|---|---|
| دستگاه استفاده شده | 1235 | 952 | 2336 |
| صفر قدم ثبت شده | 138 | 1367 | 1621 |
این سطرها از همه میانگینهای فعالیت کنار گذاشته شدند اما برای تحلیل میزان استفاده از دستگاه نگه داشته شدند، جایی که همین سطرها شاهد مستقیم آناند که ردیاب هر چند وقت یکبار استفاده میشود. وارد کردن آنها در میانگینها، فعالیت را حدود ۱۰ درصد کمتر از واقع نشان میداد.
فایل sleepDay_merged.csv تنها در پوشه آوریل وجود دارد.
استفاده تنها از آن یعنی فعالیت از هر دو ماه گرفته شود اما خواب فقط از یک
ماه. فایل minuteSleep_merged.csv ماه مارس را در سطح دقیقه
پوشش میدهد، جایی که value با ۱ برابر خواب، ۲ برابر بیقراری
و ۳ برابر بیداری کدگذاری شده و هر سطر معادل یک دقیقه در رختخواب است.
شمردن سطرها در هر شب زمان حضور در رختخواب را میدهد و شمردن
value == 1 دقایق خواب را. این دقیقاً همان دو معیاری است که
فایل آوریل گزارش میکند.
فرض اعلامشده: خواب از نیمهشب عبور میکند، پس گروهبندی بر پایه تاریخ تقویمی یک شب را به دو رکورد میشکند. کم کردن شش ساعت پیش از گرفتن تاریخ، دقایق پیش از سپیدهدم را به شب قبل نسبت میدهد. رکوردهای کمتر از ۶۰ دقیقه به عنوان تکههای ناقص کنار گذاشته شدند، نه به عنوان یک شب. این مرز ساعت ۶ بامداد یک تصمیم اجتهادی است. کنار گذاشتن کامل مارس پایه شواهد را از 882 شب به ۴۱۰ شب کاهش میدهد، بدون آنکه جهت یا معنای هیچ یافتهای تغییر کند.
tibble(
`جدول` = c("فعالیت روزانه (روزهای استفاده)", "خواب", "قدمهای ساعتی", "ثبتهای وزن"),
`سطرها` = c(nrow(worn), nrow(sleep), nrow(hourly), nrow(weight)),
`کاربران` = c(n_distinct(worn$id), n_distinct(sleep$id),
n_distinct(hourly$id), n_distinct(weight$id)),
`بازه زمانی` = c(
paste(format(min(worn$date), "%Y/%m/%d"), "تا", format(max(worn$date), "%Y/%m/%d")),
paste(format(min(sleep$date), "%Y/%m/%d"), "تا", format(max(sleep$date), "%Y/%m/%d")),
paste(format(min(as_date(hourly$ts)), "%Y/%m/%d"), "تا", format(max(as_date(hourly$ts)), "%Y/%m/%d")),
paste(format(min(weight$date), "%Y/%m/%d"), "تا", format(max(weight$date), "%Y/%m/%d"))
)
) |> kable(caption = "جدولهای پاکشده، آماده برای تحلیل")
| جدول | سطرها | کاربران | بازه زمانی |
|---|---|---|---|
| فعالیت روزانه (روزهای استفاده) | 1235 | 35 | 2016/03/12 تا 2016/05/12 |
| خواب | 882 | 25 | 2016/03/11 تا 2016/05/12 |
| قدمهای ساعتی | 46008 | 35 | 2016/03/12 تا 2016/05/12 |
| ثبتهای وزن | 98 | 13 | 2016/03/30 تا 2016/05/12 |
خروجی ۴: خلاصهای از تحلیل
تحلیل در چهار مرحله پیش رفت. آمار توصیفی سطح پایه فعالیت، خواب و کالری را در مقایسه با معیارهای منتشرشده سلامت مشخص کرد. بخشبندی در سطح کاربر شرکتکنندگان را بر پایه میانگین قدم روزانه و نرخ استفاده از دستگاه گروهبندی کرد، و همیشه ابتدا در سطح هر کاربر تجمیع شد و سپس کاربران با هم مقایسه شدند تا شرکتکنندگانی با روزهای ثبتشده بیشتر بر میانگینها غالب نشوند. تجمیع زمانی فعالیت را بر پایه ساعت شبانهروز و روز هفته بررسی کرد. همبستگی و رگرسیون رابطه میان قدم، کالری، زمان بیتحرکی و مدت خواب را آزمود.
غافلگیری اصلی: متغیری که بیشترین ارتباط را با خواب دارد
شمار قدم نیست، بلکه زمان بیتحرکی است، و این رابطه
تقریباً سه برابر قویتر است. این موضوع پیام بازاریابی را از تشویق به ورزش
به کاهش نشستن تغییر میدهد؛ سدی بهمراتب کوتاهتر برای کاربر، و ادعایی که
در حال حاضر هیچ رقیب بزرگی مطرح نمیکند.
sa <- worn |> inner_join(sleep, by = c("id", "date"))
tibble(
`رابطه` = c("قدم روزانه و کالری سوخته",
"دقایق بیتحرکی و کالری سوخته",
"قدم روزانه و ساعتهای خواب",
"دقایق بیتحرکی و ساعتهای خواب"),
`ضریب پیرسون r` = c(
round(cor(worn$total_steps, worn$calories), 3),
round(cor(worn$sedentary_minutes, worn$calories), 3),
round(cor(sa$total_steps, sa$hours_asleep), 3),
round(cor(sa$sedentary_minutes, sa$hours_asleep), 3)
),
n = c(nrow(worn), nrow(worn), nrow(sa), nrow(sa))
) |> kable(caption = "همبستگیهای آزمودهشده. تنها ارتباط؛ جهت علّی آزموده نشده است.")
| رابطه | ضریب پیرسون r | n |
|---|---|---|
| قدم روزانه و کالری سوخته | 0.563 | 1235 |
| دقایق بیتحرکی و کالری سوخته | 0.033 | 1235 |
| قدم روزانه و ساعتهای خواب | -0.150 | 603 |
| دقایق بیتحرکی و ساعتهای خواب | -0.483 | 603 |
خروجی ۵: تصویرسازیهای پشتیبان و یافتههای کلیدی
period_len <- daily |> group_by(period) |>
summarise(days = as.numeric(max(date) - min(date)) + 1, .groups = "drop")
user_window <- daily |> distinct(id, period) |>
left_join(period_len, by = "period") |>
group_by(id) |> summarise(window_days = sum(days), .groups = "drop")
usage <- worn |> count(id, name = "wear_days") |>
left_join(user_window, by = "id") |>
mutate(wear_rate = wear_days / window_days,
segment = case_when(wear_rate >= 0.80 ~ "استفاده زیاد (۸۰ تا ۱۰۰٪)",
wear_rate >= 0.50 ~ "استفاده متوسط (۵۰ تا ۷۹٪)",
TRUE ~ "استفاده کم (کمتر از ۵۰٪)") |>
factor(levels = c("استفاده زیاد (۸۰ تا ۱۰۰٪)", "استفاده متوسط (۵۰ تا ۷۹٪)",
"استفاده کم (کمتر از ۵۰٪)")))
usage_sum <- usage |> count(segment, .drop = FALSE) |> mutate(pct = n / sum(n))
ggplot(usage_sum, aes(reorder(segment, n), n, fill = segment)) +
geom_col(width = .62) +
geom_text(aes(label = paste0(n, " کاربر (", percent(pct, 1), ")")),
hjust = -0.08, size = 3.8, colour = SLATE, family = FONT_FA) +
coord_flip() + scale_y_continuous(expand = expansion(c(0, .3))) +
scale_fill_manual(values = c(TEAL, MINT, CORAL), guide = "none") +
labs(title = paste0("کاربر میانه ردیاب را تنها در ",
percent(median(usage$wear_rate), 1), " روزها استفاده کرده است"),
subtitle = "روزهای دارای قدم ثبتشده، به نسبت دوره خروجی هر کاربر",
x = NULL, y = "شمار کاربران") + theme_bb
کاربر میانه در 63% روزهای در دسترس قدم ثبت کرده است. هیچ
شرکتکنندهای دستگاه را در ۸۰٪ یا بیشتر از روزها
استفاده نکرده، و 13 کاربر کمتر از نیمی از روزها آن را استفاده
کردهاند.
چرا اهمیت دارد: ردیابی که یکسوم عمرش را در کشو میگذراند نمیتواند درباره خواب، استرس یا چرخه قاعدگی بینشی قابل اتکا بدهد. میزان استفاده، سقف محدودکننده هر قابلیت دیگری است.
user_avg <- worn |> group_by(id) |>
summarise(avg_steps = mean(total_steps), .groups = "drop") |>
mutate(level = case_when(avg_steps < 5000 ~ "کمتحرک (زیر ۵ هزار)",
avg_steps < 7500 ~ "فعالیت کم (۵ تا ۷٫۵ هزار)",
avg_steps < 10000 ~ "فعالیت متوسط (۷٫۵ تا ۱۰ هزار)",
TRUE ~ "فعال (بالای ۱۰ هزار)") |>
factor(levels = c("کمتحرک (زیر ۵ هزار)", "فعالیت کم (۵ تا ۷٫۵ هزار)",
"فعالیت متوسط (۷٫۵ تا ۱۰ هزار)", "فعال (بالای ۱۰ هزار)")))
lvl <- user_avg |> count(level, .drop = FALSE) |> mutate(pct = n / sum(n))
ggplot(lvl, aes(level, n, fill = level)) +
geom_col(width = .62) +
geom_text(aes(label = paste0(n, "\n", percent(pct, 1))),
vjust = -0.25, size = 3.6, colour = SLATE, family = FONT_FA) +
scale_y_continuous(expand = expansion(c(0, .25))) +
scale_fill_manual(values = c(CORAL, SAND, MINT, TEAL), guide = "none") +
labs(title = paste0(sum(lvl$n[1:2]), " کاربر از ", sum(lvl$n),
" کاربر بهطور میانگین زیر ۷۵۰۰ قدم در روز دارند"),
subtitle = "کاربران بر پایه میانگین قدم روزانه خودشان گروهبندی شدهاند",
x = NULL, y = "شمار کاربران") + theme_bb
میانگین قدم روزانه 8,057 و میانه 7,623 بود. تنها 34.0% روزها به ۱۰٬۰۰۰ قدم رسیدند.
worn |> group_by(weekday) |>
summarise(avg_steps = mean(total_steps), .groups = "drop") |>
ggplot(aes(weekday, avg_steps, fill = avg_steps)) +
geom_col(width = .68) +
geom_hline(yintercept = 10000, linetype = "dashed", colour = CORAL, linewidth = .7) +
annotate("text", x = 1, y = 10400, label = "هدف ۱۰٬۰۰۰ قدم",
colour = CORAL, size = 3.2, hjust = 0, family = FONT_FA) +
scale_x_discrete(labels = ROZHA) +
scale_fill_gradient(low = MINT, high = TEAL, guide = "none") +
scale_y_continuous(labels = comma, expand = expansion(c(0, .12))) +
labs(title = "هیچ روزی از هفته بهطور میانگین به هدف ۱۰٬۰۰۰ قدم نمیرسد",
subtitle = "یکشنبه کمتحرکترین روز است", x = NULL, y = "میانگین قدم") +
theme_bb
چرا اهمیت دارد: برای نیمی از کاربران، هدف پیشفرض دستنیافتنی است. هدفی که هر روز از دست میرود دیگر انگیزه نمیسازد و به یادآور شکست بدل میشود.
mins <- worn |>
summarise(`بیتحرک` = mean(sedentary_minutes), `سبک` = mean(lightly_active_minutes),
`متوسط` = mean(fairly_active_minutes), `شدید` = mean(very_active_minutes)) |>
pivot_longer(everything(), names_to = "intensity", values_to = "minutes") |>
mutate(pct = minutes / sum(minutes),
intensity = factor(intensity, levels = c("بیتحرک", "سبک", "متوسط", "شدید")))
ggplot(mins, aes("", minutes, fill = intensity)) +
geom_col(width = .55) +
geom_text(aes(label = ifelse(pct > .03, paste0(intensity, "\n", round(minutes), " دقیقه"), "")),
position = position_stack(vjust = .5), size = 3.6,
colour = "white", fontface = "bold", family = FONT_FA) +
coord_flip() + scale_fill_manual(values = c(SLATE, MINT, SAND, CORAL)) +
labs(title = paste0(percent(mins$pct[mins$intensity == "بیتحرک"], 1),
" از زمان ثبتشده بیتحرکی است"),
subtitle = "میانگین دقیقه در هر روز استفاده، به تفکیک شدت فعالیت",
x = NULL, y = "دقیقه", fill = NULL) +
theme_bb + theme(axis.text.y = element_blank())
کاربران بهطور میانگین 15.9 ساعت بیتحرکی در هر روز ثبتشده داشتهاند، در برابر 21.7 دقیقه فعالیت شدید. در 55.8% روزها، مجموع زمان فعالیت متوسط و شدید زیر ۳۰ دقیقه بوده است.
ct <- cor.test(worn$total_steps, worn$calories)
m <- lm(calories ~ total_steps, data = worn)
ggplot(worn, aes(total_steps, calories)) +
geom_point(alpha = .28, colour = TEAL, size = 1.5) +
geom_smooth(method = "lm", se = TRUE, colour = CORAL, fill = SAND) +
scale_x_continuous(labels = comma) + scale_y_continuous(labels = comma) +
labs(title = "قدم بیشتر یعنی کالری سوخته بیشتر، اما بازدهی آن اندک است",
subtitle = paste0("r = ", round(ct$estimate, 2), "، حدود ",
round(coef(m)[2] * 1000), " کالری به ازای هر ۱۰۰۰ قدم اضافه"),
x = "قدم روزانه", y = "کالری سوخته") + theme_bb
چرا اهمیت دارد: فرصت قابل هدفگیری همان 15.9 ساعت بیتحرکی است، نه 22 دقیقه فعال. شکستن دورههای نشستن برای کاربر سدی بهمراتب کوتاهتر از افزودن جلسات تمرین است.
by_hour <- hourly |> group_by(hour) |>
summarise(avg_steps = mean(step_total), .groups = "drop")
ggplot(by_hour |> mutate(peak = hour %in% c(12, 13, 17, 18, 19)),
aes(factor(hour), avg_steps, fill = peak)) +
geom_col(width = .78) +
scale_fill_manual(values = c(`FALSE` = GREY, `TRUE` = TEAL), guide = "none") +
labs(title = "فعالیت در وقت ناهار و دوباره از ساعت ۱۷ تا ۱۹ به اوج میرسد",
subtitle = "میانگین قدم در هر ساعت شبانهروز، برای همه کاربران",
x = "ساعت شبانهروز", y = "میانگین قدم") + theme_bb
by_hour |> arrange(desc(avg_steps)) |> head(5) |>
transmute(`ساعت` = paste0(hour, ":00"), `میانگین قدم` = round(avg_steps)) |>
kable(caption = "پنج ساعت پرتحرک شبانهروز")
| ساعت | میانگین قدم |
|---|---|
| 19:00 | 555 |
| 18:00 | 550 |
| 12:00 | 534 |
| 14:00 | 506 |
| 17:00 | 500 |
چرا اهمیت دارد: زمانبندی اعلانها در بیشتر اپلیکیشنها حدس و گمان است. در این بازهها کاربران از پیش در حرکتاند و بیشترین پذیرش را نسبت به یک یادآوری دارند.
ggplot(sleep, aes(hours_asleep)) +
geom_histogram(binwidth = .5, fill = TEAL, colour = "white") +
geom_vline(xintercept = 7, linetype = "dashed", colour = CORAL, linewidth = .8) +
annotate("text", x = 7.12, y = Inf, vjust = 2, hjust = 0, colour = CORAL, size = 3.4,
label = "۷ ساعت = کمینه توصیهشده", family = FONT_FA) +
labs(title = paste0(percent(mean(sleep$hours_asleep < 7), 1),
" از شبها زیر ۷ ساعت خواب دارند"),
subtitle = paste0(nrow(sleep), " شب از ", n_distinct(sleep$id), " کاربر"),
x = "ساعت خواب", y = "شمار شبها") + theme_bb
میانگین خواب 6.68 ساعت بود، با میانگین 35.2 دقیقه بیداری در رختخواب.
ggplot(sa, aes(sedentary_minutes, hours_asleep)) +
geom_point(alpha = .3, colour = TEAL, size = 1.5) +
geom_smooth(method = "lm", se = TRUE, colour = CORAL, fill = SAND) +
geom_hline(yintercept = 7, linetype = "dotted", colour = SLATE) +
labs(title = "هرچه روز بیتحرکتر، خواب آن شب کمتر",
subtitle = paste0("r = ", round(cor(sa$sedentary_minutes, sa$hours_asleep), 2),
" در ", nrow(sa), " روز کاربری تطبیقیافته"),
x = "دقایق بیتحرکی", y = "ساعت خواب") + theme_bb
چرا اهمیت دارد: این عملیترین رابطه در کل مجموعه داده است. پیام «بیشتر راه برو، بهتر بخواب» نیست، بلکه «کمتر بنشین، بهتر بخواب» است؛ ادعایی مشخص، قابل دفاع و متمایز.
adopt <- tibble(
feature = c("فعالیت (خودکار)", "خواب (استفاده شبانه)", "وزن (ثبت دستی)"),
users = c(n_distinct(daily$id), n_distinct(sleep$id), n_distinct(weight$id))
) |> mutate(pct = users / n_users)
ggplot(adopt, aes(reorder(feature, pct), pct, fill = feature)) +
geom_col(width = .6) +
geom_text(aes(label = paste0(users, " کاربر (", percent(pct, 1), ")")),
hjust = -0.08, size = 3.8, colour = SLATE, family = FONT_FA) +
coord_flip() +
scale_y_continuous(labels = percent, limits = c(0, 1.3), expand = c(0, 0)) +
scale_fill_manual(values = c(TEAL, SAND, CORAL), guide = "none") +
labs(title = "ثبت داده بهشدت افت میکند وقتی ورود دستی لازم است",
subtitle = "نسبت کاربرانی که دستکم یکبار هر نوع داده را ثبت کردهاند",
x = NULL, y = "نسبت کاربران") + theme_bb
64.3% از ثبتهای وزن با دست تایپ شدهاند، با میانه 2 ثبت برای هر کاربر ثبتکننده در کل بازه.
چرا اهمیت دارد: هر لایه اضافه از دشواری، تقریباً یکسوم کاربران را از دست میدهد. قابلیتهایی که ورود دستی میخواهند، صرفنظر از کیفیت طراحی، پذیرفته نمیشوند.
خروجی ۶: مهمترین توصیههای محتوایی در سطح کلان
۱. چه روندهایی در استفاده از دستگاههای هوشمند دیده
میشود؟ دستگاهها بهطور نامنظم استفاده میشوند، با میانه 63%
روزها، و هیچ کاربری از ۸۰٪ فراتر نمیرود. فعالیت بهروشنی
کمتر از اهداف سلامت عمومی است: تنها 34.0% روزها به ۱۰٬۰۰۰ قدم میرسد، و
15.9 ساعت از روز ثبتشده میانگین، بیتحرکی است. فعالیت حول وقت ناهار و
اوایل شب متمرکز میشود. خواب کوتاه است، 49.1% شبها زیر هفت ساعت است، و
پذیرش قابلیتها هرجا ورود دستی لازم باشد بهشدت افت میکند.
۲. این روندها چگونه بر مشتریان بلابیت قابل تعمیم است؟ مشتریان بلابیت از همان دسته دستگاهها برای همان مقاصد استفاده میکنند، پس همان سقفهای رفتاری برقرار است. دو مورد مستقیمتر منتقل میشود. نخست، نرخ استفاده سقف همه چیز است: قابلیتهای بلابیت در زمینه استرس، خواب و چرخه قاعدگی به استفاده پیوسته وابستهاند، پس فرم جواهرگونه Leaf یک محدودیت واقعی را هدف میگیرد، نه یک مسئله ظاهری. دوم، پیوند میان بیتحرکی و خواب دقیقاً با جایگاهیابی موجود بلابیت حول تندرستی کلنگر، در برابر عملکرد ورزشی، همراستاست.
۳. این روندها چگونه میتواند بر راهبرد بازاریابی بلابیت اثر بگذارد؟ پیام را از عملکرد به دستیافتنی بودن جابهجا میکنند. بهجای رقابت با فیتبیت و اپل بر سر شمار قدم و ثبت تمرین، جایی که دادهها نشان میدهد بیشتر کاربران ناکام میمانند، بلابیت میتواند ادعای کمارتفاعتر، دستیافتنیتر و بهتر مستندشده «کمتر نشستن برای بهتر خوابیدن» را از آنِ خود کند.
تنها 34.0% روزها به ۱۰٬۰۰۰ قدم میرسد و 18 کاربر از 35 کاربر بهطور میانگین زیر ۷۵۰۰ قدم دارند. هدف اولیه هر کاربر از خط پایه هفته نخست خودش تعیین و بهتدریج بالا برده شود.
پیام تبلیغاتی: «هدفی که همانجا که هستی به سراغت میآید.»
زمان بیتحرکی، خواب کوتاه را تقریباً سه برابر قویتر از شمار قدم پیشبینی میکند (r = -0.48 در برابر r = -0.15). قابلیت اصلی اپلیکیشن حول همین رابطه ساخته شود، با پیوند دادن یادآوریهای ساعتی حرکت به امتیاز خواب.
پیام تبلیغاتی: «امروز کمتر بنشین، امشب بهتر بخواب.»
فعالیت از ساعت ۱۲ تا ۱۴ و از ۱۷ تا ۱۹ به اوج میرسد. یادآوریهای حرکت در همین بازهها، برنامه روزانه در آرامش ساعت ۷ صبح، و یادآوری آمادهشدن برای خواب حدود ساعت ۲۲ ارائه شود. این کمهزینهترین توصیه از میان سه مورد و سریعترین برای سنجش است.
پیام تبلیغاتی: «یادآوری، وقتی از پیش در حرکتی.»
۴. فرم Leaf را به استدلال اصلی برای حفظ کاربر تبدیل کنید. نرخ استفاده سقف هر قابلیت دیگری است. Leaf را با پیام «ردیابی که از دستت درنمیآوری» بازاریابی کنید و روزهای پیاپی استفاده را پاداش دهید، نه قدمهای بهدستآمده را.
۵. ثبت دستی را حذف کنید. ثبت وزن تنها به 37% کاربران رسید. فرض بر این باشد که هر قابلیت با ورود دستی شکست میخورد؛ اولویت با همگامسازی خودکار آبرسانی Spring و اتصال به ترازوی هوشمند باشد.
۶. محتوای عضویت را حول میانه هفته قرار دهید. خواب و فعالیت هر دو در میانه هفته افت میکنند. محتوای مربیگری برای عصر یکشنبه و صبح سهشنبه برنامهریزی شود.
tibble(
`محدودیت` = c("اندازه نمونه", "داده جنسیت", "قدمت داده", "شیوه گردآوری",
"علیت", "تجمیع داده خواب"),
`توضیح` = c(
paste0(n_users, " شرکتکننده، بسیار کمتر از آنکه به بازار مصرف تعمیم داده شود"),
"ثبت نشده است، در حالی که بلابیت منحصراً به زنان میفروشد",
paste0("گردآوری از ", format(min(daily$date), "%Y/%m"), " تا ",
format(max(daily$date), "%Y/%m"), "، یعنی نه سال پیش"),
"داوطلبان خودانتخاب MTurk، که احتمالاً با پایگاه مشتریان بلابیت منطبق نیستند",
"پیوند بیتحرکی و خواب یک ارتباط است؛ جهت آن آزموده نشده",
"شبهای مارس بر فرض مرز ساعت ۶ بامداد استوارند"
)
) |> kable(caption = "محدودیتهای این یافتهها")
| محدودیت | توضیح |
|---|---|
| اندازه نمونه | 35 شرکتکننده، بسیار کمتر از آنکه به بازار مصرف تعمیم داده شود |
| داده جنسیت | ثبت نشده است، در حالی که بلابیت منحصراً به زنان میفروشد |
| قدمت داده | گردآوری از 2016/03 تا 2016/05، یعنی نه سال پیش |
| شیوه گردآوری | داوطلبان خودانتخاب MTurk، که احتمالاً با پایگاه مشتریان بلابیت منطبق نیستند |
| علیت | پیوند بیتحرکی و خواب یک ارتباط است؛ جهت آن آزموده نشده |
| تجمیع داده خواب | شبهای مارس بر فرض مرز ساعت ۶ بامداد استوارند |
این یافتهها باید پیش از تخصیص بودجه راستیآزمایی شوند. گامهای بعدی پیشنهادی، به ترتیب هزینه:
۱. آزمون A/B برای زمانبندی اعلانها در بازههای ۱۲ تا ۱۴ و ۱۷ تا ۱۹. کمهزینهترین برای اجرا و سریعترین برای تأیید یا رد. ۲. تکرار این تحلیل روی دادههای تلهمتری خود اپلیکیشن بلابیت، جایی که جنسیت، سن و خط محصول مشخص است؛ همان مجموعه داده افزودهای که سرشن پیشنهاد داد. ۳. آزمودن هدف سازگارشونده در برابر هدف ثابت ۱۰٬۰۰۰ قدم، با سنجش استفاده فعال در ۳۰ روز به جای شمار قدم. ۴. نظرسنجی از کاربران رهاکننده درباره اینکه چرا دستگاه را کنار گذاشتند، تا روشن شود آیا فرم دستگاه واقعاً محدودیت اصلی نرخ استفاده است یا نه.
این سند ترجمه ماشینی نسخه اصلی انگلیسی است که با یک مدل زبانی هوش مصنوعی انجام شده است. گزارش اصلی به زبان انگلیسی نوشته شده و تحلیل، کد و همه اعداد از همان نسخه اصلی میآیند.
تنها متن، عنوانها، برچسب جدولها و متن نمودارها ترجمه شدهاند. نام توابع، نام متغیرها، نام ستونها و نام فایلها به انگلیسی ماندهاند، چون بخشی از کد و مجموعه داده هستند.
در صورت هرگونه اختلاف میان این نسخه و اصل انگلیسی، نسخه انگلیسی معتبر است. این ترجمه توسط مترجم متخصص بومی بازبینی نشده است.
sessionInfo()
## R version 4.6.1 (2026-06-24 ucrt)
## Platform: x86_64-w64-mingw32/x64
## Running under: Windows 11 x64 (build 26200)
##
## Matrix products: default
## LAPACK version 3.12.1
##
## locale:
## [1] LC_COLLATE=German_Germany.utf8 LC_CTYPE=German_Germany.utf8
## [3] LC_MONETARY=German_Germany.utf8 LC_NUMERIC=C
## [5] LC_TIME=C
##
## time zone: Europe/Berlin
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] knitr_1.51 scales_1.4.0 janitor_2.2.1 lubridate_1.9.5
## [5] forcats_1.0.1 stringr_1.6.0 dplyr_1.2.1 purrr_1.2.2
## [9] readr_2.2.0 tidyr_1.3.2 tibble_3.3.1 ggplot2_4.0.3
## [13] tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] sass_0.4.10 generics_0.1.4 lattice_0.22-9 stringi_1.8.9
## [5] hms_1.1.4 digest_0.6.39 magrittr_2.0.5 evaluate_1.0.5
## [9] grid_4.6.1 timechange_0.4.0 RColorBrewer_1.1-3 fastmap_1.2.0
## [13] Matrix_1.7-5 jsonlite_2.0.0 mgcv_1.9-4 jquerylib_0.1.4
## [17] cli_3.6.6 rlang_1.3.0 crayon_1.5.3 bit64_4.8.2
## [21] splines_4.6.1 withr_3.0.3 cachem_1.1.0 yaml_2.3.12
## [25] otel_0.2.0 tools_4.6.1 parallel_4.6.1 tzdb_0.5.0
## [29] vctrs_0.7.3 R6_2.6.1 lifecycle_1.0.5 snakecase_0.11.1
## [33] bit_4.6.0 vroom_1.7.1 pkgconfig_2.0.3 pillar_1.11.1
## [37] bslib_0.12.0 gtable_0.3.6 glue_1.8.1 xfun_0.60
## [41] tidyselect_1.2.1 farver_2.1.2 nlme_3.1-169 htmltools_0.5.9
## [45] rmarkdown_2.31 labeling_0.4.3 compiler_4.6.1 S7_0.2.2