суббота, 13 июня 2020 г.

R : работа с данными

Единицей данных в нашем случае будет таблица или в объектах R - dataframe. В этой статье рассмотрим основные методы работы с данными, представленными в виде таблицы. Для примера используем дневные продажи розничного магазина одежды. Загружаем данные и смотрим на его содержание и структуру.

shop <- read_excel("ShopSale06.xlsx")
head(shop)

# A tibble: 6 x 6
  Date                            Code       Gender    Category      Season           Amt
  <dttm>                       <chr>      <chr>       <chr>    <       chr>          <dbl>
1 2019-06-01 00:00:00 SQ52420 Женский Шорты        Лето              1
2 2019-06-01 00:00:00 SQ54655 Мужской Шорты        Лето              1
3 2019-06-01 00:00:00 SQ60070 Унисекс Аксессуары  Универсал   1
4 2019-06-01 00:00:00 SQ60453 Женский Рубашка      Универсал   1
5 2019-06-01 00:00:00 SQ49034 Мужской Шорты        Лето              1
6 2019-06-01 00:00:00 SQ49786 Мужской Брюки         Лето              1

str(shop)

Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 1411 obs. of  6 variables:
 $ Date    : POSIXct, format: "2019-06-01" "2019-06-01" "2019-06-01" ...
 $ Code    : chr  "SQ52420" "SQ54655" "SQ60070" "SQ60453" ...
 $ Gender  : chr  "Женский" "Мужской" "Унисекс" "Женский" ...
 $ Category: chr  "Шорты" "Шорты" "Аксессуары" "Рубашка" ...
 $ Season  : chr  "Лето" "Лето" "Универсал" "Универсал" ...
 $ Amt     : num  1 1 1 1 1 1 1 1 1 1 ...

Добавим в него колонку дня недели

shop$Wday <- wday(shop$Date, label = T)
str(shop$Wday)


 Ord.factor w/ 7 levels "Вс"<"Пн"<"Вт"<..: 7 7 7 7 7 7 7 7 7 7 ...


Функции фильтра и выбора

Функция filter позволяет нам отбирать данные по значениям строк, а  select  -  по названиям столбцов. 

Отбираем продажи по воскресеньям и понедельникам

head(filter(shop, Wday=='Вс' | Wday=='Пн'))

# A tibble: 6 x 7
  Date                               Code    Gender  Category     Season                 Amt    Wday 
  <dttm>                            <chr>   <chr>   <chr>             <chr>                  <dbl>  <ord>
1 2019-06-02 00:00:00 SQ52607 Мужской Брюки       Лето                        1       Вс   
2 2019-06-02 00:00:00 SQ53860 Унисекс Аксессуары Универсал           1       Вс   
3 2019-06-02 00:00:00 SQ60373 Мужской Куртка        Демисезонный     1      Вс   
4 2019-06-02 00:00:00 SQ60385 Мужской Куртка        Демисезонный     1      Вс   
5 2019-06-02 00:00:00 SQ60393 Мужской Шорты       Лето                        1      Вс   

6 2019-06-02 00:00:00 SQ60402 Мужской Футболка   Лето                       1       Вс 


Отбираем продажи брюк или курток


head(filter(shop, Category=='Брюки' | Category=='Куртка'))

# A tibble: 6 x 7
  Date                Code     Gender  Category Season      Amt Wday 
  <dttm>              <chr>    <chr>   <chr>    <chr>     <dbl> <ord>
1 2019-06-01 00:00:00 SQ49786  Мужской Брюки    Лето          1 Сб   
2 2019-06-01 00:00:00 SQ57751  Женский Брюки    Универсал     1 Сб   
3 2019-06-01 00:00:00 SQ57759  Мужской Брюки    Универсал     1 Сб   
4 2019-06-01 00:00:00 SQ058698 Мужской Брюки    Универсал     1 Сб   
5 2019-06-01 00:00:00 SQ60703  Мужской Брюки    Универсал     1 Сб   

6 2019-06-01 00:00:00 SQ60712  Мужской Брюки    Универсал     1 Сб  


Отбираем продажи в штуках от 2 до 3


head(filter(shop, Amt>=2 & Amt<=3))

# A tibble: 6 x 7
  Date                               Code    Gender  Category     Season         Amt    Wday 
  <dttm>                            <chr>   <chr>   <chr>              <chr>         <dbl>   <ord>
1 2019-06-01 00:00:00 SQ61091 Мужской Брюки    Универсал        2         Сб   
2 2019-06-01 00:00:00 SQ61181 Женский Шорты    Лето                  2         Сб   
3 2019-06-02 00:00:00 SQ49034 Мужской Шорты    Лето                  2         Вс   
4 2019-06-02 00:00:00 SQ60850 Мужской Рубашка  Лето                 2          Вс   
5 2019-06-02 00:00:00 SQ60996 Мужской Рубашка  Лето                 2          Вс   
6 2019-06-02 00:00:00 SQ61156 Женский Футболка Лето                2          Вс 


Отбираем брюки с продажами от двух единиц


head(filter(shop, Amt>=2 & Category=='Брюки'))

# A tibble: 6 x 7
  Date                             Code    Gender  Category       Season       Amt      Wday 
  <dttm>                          <chr>   <chr>   <chr>               <chr>       <dbl>    <ord>
1 2019-06-01 00:00:00 SQ61091 Мужской Брюки    Универсал     2          Сб   
2 2019-06-02 00:00:00 SQ58169 Мужской Брюки    Лето                2          Вс   
3 2019-06-08 00:00:00 SQ59159 Мужской Брюки    Универсал     2          Сб   
4 2019-06-08 00:00:00 SQ59692 Женский Брюки    Лето                2          Сб   
5 2019-06-09 00:00:00 SQ52607 Мужской Брюки    Лето                2          Вс   
6 2019-06-09 00:00:00 SQ57759 Мужской Брюки    Универсал     2          Вс   


Отбираем колонки с полом, категорией и продажами

head(select(shop,Gender,Category,Amt)) 

# A tibble: 6 x 3
  Gender         Category       Amt
  <chr>             <chr>          <dbl>
1 Женский     Шорты          1
2 Мужской     Шорты          1
3 Унисекс     Аксессуары    1
4 Женский     Рубашка        1
5 Мужской     Шорты          1
6 Мужской     Брюки           1


Операторы %>% и %in%

Оператор %>% задает конвейерное выполнение функций, например нужно отобрать продажи брюк и вывести только три колонки

shop %>% 
  filter(Category=='Брюки') %>% 

  select(Gender,Category,Amt) 

# A tibble: 198 x 3
   Gender  Category   Amt
   <chr>   <chr>    <dbl>
 1 Мужской Брюки        1
 2 Женский Брюки        1
 3 Мужской Брюки        1
 4 Мужской Брюки        1
 5 Мужской Брюки        1
 6 Мужской Брюки        1
 7 Женский Брюки        1
 8 Мужской Брюки        1
 9 Мужской Брюки        2
10 Мужской Брюки        1

# ... with 188 more rows


Оператор %in% это оператор проверки на включение, его удобно использовать в фильтрах, например отбор продаж брюк и футболок  больше двух единиц 

shop %>% 
  filter(Amt>=2 & Category %in% c('Брюки','Футболка')) %>% 

  select(Gender,Category,Amt) 


# A tibble: 41 x 3
   Gender  Category   Amt
   <chr>   <chr>    <dbl>
 1 Мужской Брюки        2
 2 Женский Футболка     2
 3 Мужской Футболка     2
 4 Мужской Брюки        2
 5 Мужской Футболка     2
 6 Женский Футболка     2
 7 Мужской Футболка     2
 8 Мужской Брюки        2
 9 Женский Брюки        2
10 Мужской Брюки        2
# ... with 31 more rows


Создание новых переменных с помощью функции

Mutate


Часто возникает необходимость создать новую колонку по значениям в других колонках, например мы хотим ввести колонку рабочий / выходной, чтобы потом можно было легче группировать продажи по рабочим и выходным дням. Для этого используется функция mutate

shop <- shop %>% 
        mutate(WH = ifelse(Wday %in% c('Сб','Вс'),1,0))


head(shop[,c('Wday','WH')])

# A tibble: 6 x 2
  Wday     WH
  <ord> <dbl>
1 Сб        1
2 Сб        1
3 Сб        1
4 Сб        1
5 Сб        1

6 Сб        1

Агрегирование данных с использованием функций Summarize и Group_By

Функции summarize и group_by дают возможность агрегировать данные, например с помощью summarize выведем суммарные продажи и средние продажи на артикул

shop %>% 
  summarise(sum_amt = sum(Amt,na.rm=T), avg_amt = mean(Amt,na.rm=T))


# A tibble: 1 x 2

  sum_amt avg_amt

    <dbl>   <dbl>

1    1681    1.20

А в связке с функцией group_by можно выводить агрегированную информацию в разрезе определенных группировок. Например выведем суммарные и средние продажи по рабочим и выходным дням

shop %>% 
  group_by(WH) %>% 
  summarise(sum_amt = sum(Amt,na.rm=T), 
            avg_amt = mean(Amt,na.rm=T))



# A tibble: 2 x 3

     WH sum_amt avg_amt

  <dbl>   <dbl>   <dbl>

1     0     896    1.18

2     1     785    1.22


Можно делать и более сложные группировки, например суммарные и средние продажи в разрезе пола и категории

shop %>% 
  group_by(Gender,Category) %>% 
  summarise(sum_amt = sum(Amt,na.rm=T), 
            avg_amt = mean(Amt,na.rm=T))


# A tibble: 17 x 4
# Groups:   Gender [3]
   Gender       Category           sum_amt avg_amt
   <chr>           <chr>                    <dbl>   <dbl>
 1 Женский  Аксессуары              119   1.42 
 2 Женский  Брюки                        67    1.08 
 3 Женский  Куртка                       22     1.05 
 4 Женский  Платье                      25     1.09 
 5 Женский  Рубашка                   29     1    
 6 Женский  Толстовка                22     1    
 7 Женский  Футболка                224    1.04 
 8 Женский  Шорты                     46     1.12 
 9 Женский  Юбка                         7      1    
10 Мужской  Аксессуары          418    1.79 
11 Мужской  Брюки                   145     1.08 
12 Мужской  Куртка                    25      0.926
13 Мужской  Рубашка                77      1.12 
14 Мужской  Толстовка             10      1.11 
15 Мужской  Футболка             297     1.04 
16 Мужской  Шорты                 106     1.09 
17 Унисекс   Аксессуары            42    1 


Обработка пропущенных значений

Довольно часто в данных встречаются пропущенные значения, особенно это характерно для дневного количества посетителей в розничном магазине. Рассмотрим работу с пропущенными значениями на конкретном примере. Загружаем датафрейм и смотрим его содержание и структуру

shop <- read_excel("ShopDay2019.xlsx")
head(shop)

# A tibble: 6 x 6
  ds                              month  wday    wh visit     visit_na
  <dttm>                       <dbl> <dbl> <dbl> <dbl>    <dbl>
1 2019-01-01 00:00:00     1      2          1   159           159
2 2019-01-02 00:00:00     1      3          1   677           677
3 2019-01-03 00:00:00     1      4          1   643           643
4 2019-01-04 00:00:00     1      5          1   614           614
5 2019-01-05 00:00:00     1      6          1   561           561

6 2019-01-06 00:00:00     1      7          1   558           558


str(shop)

Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 365 obs. of  6 variables:
 $ ds      : POSIXct, format: "2019-01-01" "2019-01-02" "2019-01-03" ...
 $ month   : num  1 1 1 1 1 1 1 1 1 1 ...
 $ wday    : num  2 3 4 5 6 7 1 2 3 4 ...
 $ wh      : num  1 1 1 1 1 1 1 1 0 0 ...
 $ visit   : num  159 677 643 614 561 558 497 462 231 241 ...

 $ visit_na: num  159 677 643 614 561 558 497 462 231 241 ...

Перед нами данные о дневной посещаемости магазина одежды :
                               ds - дата 
                         month - месяц
                          wday  - день недели  
                           wh  - рабочий день - 0 , выходной - 1
                          visit  - дневное количество посетителей изначальное  
                          visit_na - дневное количество посетителей с искусственно сделанными                                           пропусками

Проверим, есть ли в датафрейме пропущенные значения

sum(is.na(shop))


[1] 16

В какой колонке они находятся

colSums(is.na(shop))


      ds    month     wday       wh    visit visit_na 

       0        0               0            0        0       16 



С помощью функций из пакетов  VIM и Amelia можно визуализировать присутствие пропущенных значений


library(VIM)
aggr(shop, numbers=TRUE)


library(Amelia)
missmap(shop)


Получилась очень эффектная и понятная демонстрация. Дальше существует два варианта действий, либо удалить дни с пропущенными значениями, что крайне нежелательно, либо заменить некоторым значением, например средним или медианным значениями посещаемости в данном месяце в зависимости от дня недели, мы будем рассматривать деление на выходные или рабочие дни. Для начала сохраним вектор индексов строк с пропущенными значениями :

shop_na <- is.na(shop$visit_na)
shop[shop_na,]

# A tibble: 16 x 6
   ds                  month  wday    wh visit visit_na
   <dttm>              <dbl> <dbl> <dbl> <dbl>    <dbl>
 1 2019-01-20 00:00:00     1     7     1   556       NA
 2 2019-02-20 00:00:00     2     3     0   135       NA
 3 2019-03-05 00:00:00     3     2     0   179       NA
 4 2019-03-23 00:00:00     3     6     1   423       NA
 5 2019-04-06 00:00:00     4     6     1   408       NA
 6 2019-05-24 00:00:00     5     5     0   199       NA
 7 2019-06-07 00:00:00     6     5     0   211       NA
 8 2019-06-09 00:00:00     6     7     1   349       NA
 9 2019-07-05 00:00:00     7     5     0   217       NA
10 2019-08-28 00:00:00     8     3     0   229       NA
11 2019-09-20 00:00:00     9     5     0   238       NA
12 2019-10-25 00:00:00    10     5     0   275       NA
13 2019-11-07 00:00:00    11     4     0   249       NA
14 2019-11-23 00:00:00    11     6     1   627       NA
15 2019-11-28 00:00:00    11     4     0   248       NA
16 2019-12-17 00:00:00    12     2     0   222       NA

Создадим датафрейм со средними и медианными значениями посетителей по месяцам и рабочим и выходным дням


visit_wh <- shop %>% 
  group_by(month,wh) %>% 
  summarise(avg_visit = mean(visit_na,na.rm=T),

                      med_visit = median(visit_na,na.rm=T))




visit_wh


# A tibble: 24 x 4
# Groups:   month [12]
   month    wh avg_visit med_visit
   <dbl> <dbl>     <dbl>     <dbl>
 1     1     0      237.      231 
 2     1     1      536.      559 
 3     2     0      140.      136 
 4     2     1      328.      328.
 5     3     0      181.      179 
 6     3     1      424.      432.
 7     4     0      173.      172 
 8     4     1      415.      421 
 9     5     0      177.      177 
10     5     1      381.      419 

# ... with 14 more rows

Добавим в наш датафрейм две колонки, в которых вместо пропущенных значений будут средние и медианные значения


shop$visit_na_mean <- shop$visit_na 
shop$visit_na_median <- shop$visit_na 

shop[shop_na,c(1,6:8)]


# A tibble: 16 x 5

   ds                  visit visit_na visit_na_mean visit_na_median

   <dttm>              <dbl>    <dbl>         <dbl>           <dbl>
 1 2019-01-20 00:00:00   556       NA            NA              NA
 2 2019-02-20 00:00:00   135       NA            NA              NA
 3 2019-03-05 00:00:00   179       NA            NA              NA
 4 2019-03-23 00:00:00   423       NA            NA              NA
 5 2019-04-06 00:00:00   408       NA            NA              NA
 6 2019-05-24 00:00:00   199       NA            NA              NA
 7 2019-06-07 00:00:00   211       NA            NA              NA
 8 2019-06-09 00:00:00   349       NA            NA              NA
 9 2019-07-05 00:00:00   217       NA            NA              NA
10 2019-08-28 00:00:00   229       NA            NA              NA
11 2019-09-20 00:00:00   238       NA            NA              NA
12 2019-10-25 00:00:00   275       NA            NA              NA
13 2019-11-07 00:00:00   249       NA            NA              NA
14 2019-11-23 00:00:00   627       NA            NA              NA
15 2019-11-28 00:00:00   248       NA            NA              NA
16 2019-12-17 00:00:00   222       NA            NA              NA

И с помощью вложенного цикла заменим пропущенные значения  



for(i in which(is.na(shop$visit_na))){
  for(j in 1:nrow(visit_wh)){
    if(shop$month[i]==visit_wh$month[j] & 
       shop$wh[i]==visit_wh$wh[j] ){
       shop$visit_na_mean[i] <- visit_wh$avg_visit[j]
       shop$visit_na_median[i] <- visit_wh$med_visit[j]
      break
    }
  }
}



Посмотрим на результат замены

shop[shop_na,c(1,5:8)]

# A tibble: 16 x 5
   ds                  visit visit_na visit_na_mean visit_na_median
   <dttm>              <dbl>    <dbl>         <dbl>           <dbl>
 1 2019-01-20 00:00:00   556       NA          536.            559 
 2 2019-02-20 00:00:00   135       NA          140.            136 
 3 2019-03-05 00:00:00   179       NA          181.            179 
 4 2019-03-23 00:00:00   423       NA          424.            432.
 5 2019-04-06 00:00:00   408       NA          415.            421 
 6 2019-05-24 00:00:00   199       NA          177.            177 
 7 2019-06-07 00:00:00   211       NA          205.            206 
 8 2019-06-09 00:00:00   349       NA          348.            346 
 9 2019-07-05 00:00:00   217       NA          210.            212 
10 2019-08-28 00:00:00   229       NA          229.            230 
11 2019-09-20 00:00:00   238       NA          193.            187 
12 2019-10-25 00:00:00   275       NA          224.            217 
13 2019-11-07 00:00:00   249       NA          257.            242.
14 2019-11-23 00:00:00   627       NA          644.            637 
15 2019-11-28 00:00:00   248       NA          257.            242.
16 2019-12-17 00:00:00   222       NA          235.            228 

Как видим, результат получился удовлетворительный, восстановленные значения довольно близки к фактическим. Оценим среднюю абсолютную ошибку в процентах MAPE  при замене средним и медианой

mape_na <- c(mean(abs(shop[shop_na,]$visit - shop[shop_na,]$visit_na_mean) / 
                    shop[shop_na,]$visit),
             mean(abs(shop[shop_na,]$visit - shop[shop_na,]$visit_na_median) / 
                    shop[shop_na,]$visit))

round(mape_na,3)

[1] 0.051 0.047

При замене на среднее значение ошибка составила 5.1%, при замене на медианное значение ошибка получилась 4.7%, то есть можно сделать вывод о предпочтительности медианного значения, как менее зависимого от возможных выбросов.


Длинный и широкий формат данных

Одни и те же данные могут быть представлены в широком или длинном формате. Широкий формат - каждая строка содержит информацию о нескольких наблюдениях. Длинный формат - каждая строка содержит информацию о единственном наблюдении. Широкий формат удобнее для чтения и презентации данных в табличном виде, но сложнее для обработки. Длинный формат не удобен для чтения, но значительно удобнее для обработки и визуализации. 
Пакет tidyr есть две функции предназначенных для трансформации таблиц: 
           pivot_longer() — функция, преобразующая данные из широкого формата в длинный;
           pivot_wider() — функция, преобразующая данные из длинного формата в широкий. Операция обратная той, которую осуществляет функция pivot_longer().

Рассмотрим конкретный пример - дневные продажи в розничном магазине одежды

shop_wide <- read_excel("ShopSaleWide.xlsx")


head(shop_wide)

# A tibble: 6 x 11

  Дата                Пол     Аксессуары Брюки Куртка Платье Рубашка Толстовка Футболка Шорты  Юбка
  <dttm>              <chr>        <dbl>      <dbl>    <dbl>    <dbl>     <dbl>        <dbl>         <dbl>        <dbl>  <dbl>
1 2019-06-01 Женский          3               3          NA          3             5               1                 6                5         1
2 2019-06-01 Мужской          4             12            2          NA           3             NA               11               5        NA
3 2019-06-01  Унисекс          1             NA          NA         NA         NA            NA              NA              NA     NA
4 2019-06-02  Женский          3             1            1             1            NA             1               10               NA     NA
5 2019-06-02  Мужской          7            10           2           NA           10              2               10                4       NA

6 2019-06-02  Унисекс          2              NA        NA          NA           NA           NA              NA             NA     NA

Как видим, это типичный пример широких данных, в каждой строке находится несколько наблюдений. Преобразуем эту таблицу в длинный формат с помощью функции  pivot_longer


shop_long <- shop_wide %>% 
              pivot_longer(cols = `Аксессуары`:`Юбка`, 
              names_to = "Категория", 
              values_to = "Продажи",
              values_drop_na = TRUE)


head(shop_long)

# A tibble: 6 x 4
  Дата                Пол     Категория    Продажи
  <dttm>              <chr>   <chr>             <dbl>
1 2019-06-01 Женский Аксессуары       3
2 2019-06-01 Женский Брюки                 3
3 2019-06-01 Женский Платье                3
4 2019-06-01 Женский Рубашка             5
5 2019-06-01 Женский Толстовка          1

6 2019-06-01 Женский Футболка           6



Аргументы функции pivot_longer()

  • Первый аргумент cols, описывает, какие столбцы необходимо объединить. 
  • Аргумент names_to дает имя переменной, которая будет создана из имён столбцов, которые мы объединили.
  • values_to дает имя переменной, которая будет создана из данных, хранящихся в значениях ячеек объединённых столбцов.

  • values_drop_na убирает строки с пропущенными значениями

Теперь вернем данные к исходному состоянию с помощью функции pivot_wider


shop_wide_new <- shop_long %>% 
                pivot_wider(id_cols = c(Дата,Пол, Категория), 
                names_from = Категория,
                values_from = Продажи,
                values_fill = list(Продажи = 0))


# A tibble: 6 x 11

  Дата                Пол     Аксессуары Брюки Платье Рубашка Толстовка Футболка Шорты  Юбка Куртка
  <dttm>              <chr>        <dbl>       <dbl>    <dbl>      <dbl>        <dbl>          <dbl>     <dbl>   <dbl>  <dbl>
1 2019-06-01  Женский          3               3           3              5               1                  6             5           1      0
2 2019-06-01  Мужской          4              12          0              3               0                 11            5           0      2
3 2019-06-01  Унисекс           1               0            0             0               0                  0             0           0      0
4 2019-06-02  Женский          3               1           1              0               1                 10            0           0      1
5 2019-06-02  Мужской          7             10           0             10              2                 10            4           0      2

6 2019-06-02  Унисекс           2               0           0              0               0                   0             0           0      0


Аргументы функции pivot_wider()

  • Первый аргумент id_cols, задает, какие столбцы не будут изменяться 
  • Аргумент names_from дает имя переменной, из которой будут получены названия новых столбцов
  • values_from дает имя переменной, из которой будут получены значения для новых столбцов

  • values_fill  заменяет пропущенные значения на заданные значения, в данном случае на ноль













































Комментариев нет:

Отправить комментарий