Задача: аренда велосипедов

Представьте, что у вас есть компания, занимающаяся краткосрочной арендой велосипедов. Вы набрали данные по статистике использования и теперь хотите проанализировать закономерности – когда велосипедов нужно больше, когда меньше. Например, вы планируете техосмотр и вам нужно понять, когда вы можете “безболезненно” убрать часть велосипедов с улиц.

Предупреждение: Решение данного задания не требует предварительной подготовки и опыта работы с языком 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 состоит из семи столбцов:

  • cnt - число арендованных велосипедов в этот час
  • t1 - фактическая температура
  • t2 - температура “ощущается как”
  • hum - влажность
  • wind_speed - скорость ветра km/h
  • weather_code - код типа погоды
  • is_holiday - праздник или нет (1-0)

Расшифровка кодов погоды хранится в отдельном файле 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

Что-то странное:

Что делать?

Давайте выделим пары день/выходной для подсчета дней в каждом году. Вам может показаться странным данный выбор (год/выходной) вместо день/выходной - это было сделано в силу того, что данные представлены по часам, а количество наблюдений по бинарной переменной 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)