Представьте, что у вас есть компания, занимающаяся краткосрочной арендой велосипедов. Вы набрали данные по статистике использования и теперь хотите проанализировать закономерности – когда велосипедов нужно больше, когда меньше. Например, вы планируете техосмотр и вам нужно понять, когда вы можете “безболезненно” убрать часть велосипедов с улиц.
Предупреждение: Решение данного задания не требует предварительной подготовки и опыта работы с языком R, но авторы постарались поделиться с вами новым форматом, который может стать эффективным введением в концепцию отчетов в RMarkdown
Аренда велосипедов в Лондоне в 2015-2017 гг. Каждая строка – это данные за час про количество арендованных велосипедов, погоду и праздник в этот день или нет.
Как вы можете заметить, исходный датасет содержит данные трёх типов - целочисленные, числовые (целочисленные и дробные), строковые данные. Но датасет, предоставленный нами был немного обработан, что исключило необходимость дополнительного вмешательства с вашей стороны:)
Давайте посмотрим на структуру датасета:
bikes = read.csv("~/shared/minor2_2020/1-Intro/lab05-recap/london_bikes.csv")
str(bikes)
## 'data.frame': 17414 obs. of 8 variables:
## $ timestamp : chr "2015-01-04 00:00:00" "2015-01-04 01:00:00" "2015-01-04 02:00:00" "2015-01-04 03:00:00" ...
## $ cnt : int 182 138 134 72 47 46 51 75 131 301 ...
## $ t1 : num 3 3 2.5 2 2 2 1 1 1.5 2 ...
## $ t2 : num 2 2.5 2.5 2 0 2 -1 -1 -1 -0.5 ...
## $ hum : num 93 93 96.5 100 93 93 100 100 96.5 100 ...
## $ wind_speed : num 6 5 0 0 6.5 4 7 7 8 9 ...
## $ weather_code: int 3 1 1 1 1 1 4 4 4 3 ...
## $ is_holiday : int 0 0 0 0 0 0 0 0 0 0 ...
Можем заметить, что данные в столбце timestamp даны в строковом формате
Исходный датасет bikes состоит из семи столбцов:
Расшифровка кодов погоды хранится в отдельном файле weather - требуется объединение двух таблиц по общему столбцу weather_code. Доплнительной работы с данным датастетом от вас не ожидалось, но для вас, интересующихся анализом данных, авторы решили оставить код с шаггами обработки датасета, чтобы вы смогли изучить что-то новое или закрепить известные вам до этого момента трюки.
weather = read.csv("~/shared/minor2_2020/1-Intro/lab05-recap/weather.csv",
stringsAsFactors = T)
Соответственно, чтобы работать со словами, а не кодами, нужно соединить данные.
library(dplyr)
bikes = bikes %>% inner_join(weather, by = "weather_code")
bikes = bikes %>% select(-weather_code)
Нужно ли менять где-то тип переменных, чтобы они соответствовали смыслу данных, т.е. реальному миру?
Ответ: Да
Переходим к работе с датами: нужно преобразовать к формату дат. Можем заметить, что тип данных столбца был изменён на POSIXct (тип временных данных)
library(lubridate)
bikes$timestamp = ymd_hms(bikes$timestamp)
str(bikes)
## 'data.frame': 17414 obs. of 8 variables:
## $ timestamp : POSIXct, format: "2015-01-04 00:00:00" "2015-01-04 01:00:00" ...
## $ cnt : int 182 138 134 72 47 46 51 75 131 301 ...
## $ t1 : num 3 3 2.5 2 2 2 1 1 1.5 2 ...
## $ t2 : num 2 2.5 2.5 2 0 2 -1 -1 -1 -0.5 ...
## $ hum : num 93 93 96.5 100 93 93 100 100 96.5 100 ...
## $ wind_speed: num 6 5 0 0 6.5 4 7 7 8 9 ...
## $ is_holiday: int 0 0 0 0 0 0 0 0 0 0 ...
## $ weather : Factor w/ 8 levels "broken clouds",..: 1 2 2 2 2 2 3 3 3 1 ...
После работы над типами данных мы разбили столбец timestamp на три отдельных столбца с соответствующими компонентами: день, месяц, год.
Данные преобразования были проделаны в целях исключения необходимости работы со специальными пакетами и функциями для работы с датами
Выделим год-месяц-день в отдельные переменные
bikes = bikes %>% mutate(year = year(timestamp),
month = month(timestamp),
day = day(timestamp))
Вопрос 1: посчитайте, сколько празничных дней в каждом году?
count(bikes, year, is_holiday)
## year is_holiday n
## 1 2015 0 8475
## 2 2015 1 168
## 3 2016 0 8507
## 4 2016 1 192
## 5 2017 0 48
## 6 2017 1 24
Что-то странное:
слишком большие числа - в году не может быть столько дней :)
2017 слишком отличается
Что делать?
Давайте выделим пары день/выходной для подсчета дней в каждом году. Вам может показаться странным данный выбор (год/выходной) вместо день/выходной - это было сделано в силу того, что данные представлены по часам, а количество наблюдений по бинарной переменной is_holiday соответстуют количеству зарегистрированных дней в году.
bikes %>% select(year,month,day,is_holiday) %>% unique() %>% count(year, is_holiday)
## year is_holiday n
## 1 2015 0 355
## 2 2015 1 7
## 3 2016 0 357
## 4 2016 1 8
## 5 2017 0 2
## 6 2017 1 1
Вопрос 2: Найти небольшую аномалию/выбросы в полученных результатах, и убрать ненужные данные (обратите внимание на количество наблюдений в каждом из представленных годов)
Как было отмечено немного выше, в датасете очень мало наблюдений в 2017 (что выяснилось после выделения количеств выходных дней в каждом году) - это и подразумевалось под этим вопросом.
Если бы мы строили график про количество наблюдений по годам, то картинка тоже была бы урезанная:
library(ggplot2)
ggplot(bikes) + geom_bar(aes(x = year))
Для удаления наблюдений 2017 года можно быстро посчитать границы дат в каждом году (пользуемся тем, что у дат есть порядок)
В этой таблице можем увидеть первый и последний дни каждого года нашей выборки:
bikes %>% group_by(year) %>% summarise(start = min(timestamp), end = max(timestamp))
## `summarise()` ungrouping output (override with `.groups` argument)
## # A tibble: 3 x 3
## year start end
## <dbl> <dttm> <dttm>
## 1 2015 2015-01-04 00:00:00 2015-12-31 23:00:00
## 2 2016 2016-01-01 00:00:00 2016-12-31 23:00:00
## 3 2017 2017-01-01 00:00:00 2017-01-03 23:00:00
То есть в 2017 данные только за три дня. Удалим их
bikes = bikes %>% filter(year < 2017)
Вопрос 3: правда ли, что в праздники арендуют больше, чем в обычные дни?
Еще раз – данные у нас по часам, если хотим делать выводы на уровне дня, нужно сгруппировать по дню:
bikes_day = bikes %>% group_by(year, month, day) %>%
summarise(cnt = sum(cnt),is_holiday = first(is_holiday))
bikes_day$is_holiday = as.logical(bikes_day$is_holiday)
Нет, в обычные дни спрос на велосипеды немного выше - это можно увидеть по расположению боксплотов и линий медианы, которые выделены жирной линией в теле ящичковых диаграмм.
ggplot(bikes_day) + geom_boxplot(aes(x = is_holiday, y = cnt)) +
scale_x_discrete(breaks=c("FALSE","TRUE"), labels=c("Не праздник", "Праздник")) +
xlab("")+
ylab("Число арендованных велосипедов")
Построение: графика не является принципиальным способом сравнения - можно вычислить медианы и средние арифметические по столбцу cnt
Вопрос 3: добавим к праздникам выходные. Правда ли, что в праздники и выходные арендуют больше, чем в обычные дни?
bikes = bikes %>% mutate(date = floor_date(timestamp, "day"))
bikes_day = bikes %>% group_by(date) %>% summarise(cnt = sum(cnt),
is_holiday = first(is_holiday))
Добавляем выходные:
bikes_day = bikes_day %>% mutate(weekday = wday(date, label = T))
levels(bikes_day$weekday)
## [1] "Sun" "Mon" "Tue" "Wed" "Thu" "Fri" "Sat"
bikes_day = bikes_day %>%
mutate(is_weekend_holiday = is_holiday | weekday == "Sat" | weekday == "Sun")
ggplot(bikes_day) + geom_boxplot(aes(x = factor(is_weekend_holiday), y = cnt)) +
scale_x_discrete(breaks=c("FALSE","TRUE"), labels=c("Не выходной", "Выходной")) +
xlab("")+
ylab("Число арендованных велосипедов")
Выводы?
Вопрос 4: связано ли количество арендованных велосипедов и температура?
bikes%>% group_by(date,is_holiday)%>% summarise(t1=mean(t1),t2=mean(t2),sum_bike= sum(cnt)) %>% ggplot()+ geom_point(aes(x=t2,y= sum_bike,color=is_holiday),size = 0.5,alpha = 0.6)+theme(legend.position="none")
## `summarise()` regrouping output by 'date' (override with `.groups` argument)