Описание набора данных

Данный пример подготовлен на основе набора данных Missing Migrants Dataset. Данные получены из Международной организации по миграции и являются частью проекта под названием Missing Migrants, цель которого - отслеживание случаев гибели мигрантов, в том числе беженцев, в процессе перемещения по миграционным маршрутам по всему миру. Исследования в рамках этого проекта начались после трагедии в октябре 2013 года, когда, по крайней мере, 368 человек погибли в результате двух кораблекрушений возле итальянского острова Лампедуза. С тех пор проект Missing Migrants превратился в важный источник, предоставляющий доступ СМИ, исследователям и широкой общественности к самой последней информации о пропавших мигрантах.

Данные имеют следующую структуру:
1. id - идентификатор инцидента
2. cause_of_death - причина смерти
3. region_origin - регион происхождения мигрантов
4. affected_nationality - национальность мигрантов
5. missing - количество пропавших без вести
6. dead - количество погибших
7. incident_region - регион, в котором произошел инцидент
8. date - дата, когда зафиксирован инцидент. В наборе данных собраны записи за период с 2014 года по июнь 2017.
9. source - источник информации
10. reliability - достоверность информации
11. lat - географическая широта места, где произошел инцидент
12. lon - географическая долгота, где произошел инцидент

library(tidyverse)
## -- Attaching packages -------------------------------------------------------------------------------------------------------------- tidyverse 1.2.1 --
## v ggplot2 3.2.1     v purrr   0.3.2
## v tibble  2.1.3     v dplyr   0.8.3
## v tidyr   0.8.3     v stringr 1.4.0
## v readr   1.3.1     v forcats 0.4.0
## -- Conflicts ----------------------------------------------------------------------------------------------------------------- tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()
mm <- read_csv("data/MissingMigrantsProject.csv")
## Parsed with column specification:
## cols(
##   id = col_double(),
##   cause_of_death = col_character(),
##   region_origin = col_character(),
##   affected_nationality = col_character(),
##   missing = col_double(),
##   dead = col_double(),
##   incident_region = col_character(),
##   date = col_character(),
##   source = col_character(),
##   reliability = col_character(),
##   lat = col_double(),
##   lon = col_double()
## )

Очистка данных

В загруженном наборе данных 2420 строк и 12 столбцов. Первые 10 строк выглядят следующим образом:

head(mm, 10)
## # A tibble: 10 x 12
##       id cause_of_death region_origin affected_nation~ missing  dead
##    <dbl> <chr>          <chr>         <chr>              <dbl> <dbl>
##  1     1 Presumed drow~ Middle East   Iraq                   1     1
##  2     3 Fell from tra~ Central Amer~ Honduras              NA     1
##  3     4 Presumed drow~ Middle East   <NA>                  NA     1
##  4     6 Drowning       MENA          <NA>                   6     4
##  5     7 Vehicle accid~ South East A~ Cambodia              NA     4
##  6     8 Drowning       MENA          <NA>                  NA    11
##  7     9 Drowning       MENA          <NA>                  NA     1
##  8    10 Drowning       MENA          <NA>                  NA     1
##  9    11 Drowning       <NA>          <NA>                   0     3
## 10    12 Died of unkno~ MENA          Syria                 NA     1
## # ... with 6 more variables: incident_region <chr>, date <chr>,
## #   source <chr>, reliability <chr>, lat <dbl>, lon <dbl>

С помощью функции summary() получим краткую статистическую сводку о распределении значений в исходном наборе данных:

summary(mm)
##        id         cause_of_death     region_origin     
##  Min.   :     1   Length:2420        Length:2420       
##  1st Qu.: 28571   Class :character   Class :character  
##  Median :121178   Mode  :character   Mode  :character  
##  Mean   : 95926                                        
##  3rd Qu.:144678                                        
##  Max.   :184750                                        
##                                                        
##  affected_nationality    missing            dead        
##  Length:2420          Min.   :  0.00   Min.   :  0.000  
##  Class :character     1st Qu.:  3.00   1st Qu.:  1.000  
##  Mode  :character     Median : 10.00   Median :  1.000  
##                       Mean   : 39.66   Mean   :  4.729  
##                       3rd Qu.: 33.00   3rd Qu.:  3.000  
##                       Max.   :750.00   Max.   :750.000  
##                       NA's   :2149     NA's   :102      
##  incident_region        date              source         
##  Length:2420        Length:2420        Length:2420       
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
##                                                          
##                                                          
##  reliability             lat              lon         
##  Length:2420        Min.   :-26.22   Min.   :-117.07  
##  Class :character   1st Qu.: 19.54   1st Qu.: -97.04  
##  Mode  :character   Median : 29.35   Median :  14.47  
##                     Mean   : 26.90   Mean   : -14.00  
##                     3rd Qu.: 34.04   3rd Qu.:  32.01  
##                     Max.   : 66.97   Max.   : 116.22  
##                     NA's   :4        NA's   :4

Для числовых столбцов набора данных в сводке можно увидеть минимальные, максимальные значения, а также среднее, медиану, 1 и 3 квартиль, количество неизвестных значений (NA). Например, среднее количество пропавших без вести - 39.6568266 человек, при этом в 2149 случаях из 2420 количество пропавших неизвестно. Рассчитаем для каждого из столбцов количество неизвестных значений. Для этого нам нужно для каждого из столбцов проверить каждое значение, является ли оно неизвестным, с помощью функции is.na(). Функция is.na() для каждого значения вернет true или false. Далее с помощью функции sum() мы просуммируем значения true, выполнив эту операцию для каждого из столбцов набора данных mm с помощью функции lapply():

lapply(mm, FUN = function(x) {sum(is.na(x))})
## $id
## [1] 0
## 
## $cause_of_death
## [1] 203
## 
## $region_origin
## [1] 443
## 
## $affected_nationality
## [1] 1575
## 
## $missing
## [1] 2149
## 
## $dead
## [1] 102
## 
## $incident_region
## [1] 10
## 
## $date
## [1] 9
## 
## $source
## [1] 7
## 
## $reliability
## [1] 324
## 
## $lat
## [1] 4
## 
## $lon
## [1] 4

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

condition <- !is.na(mm$missing) ## вектор, в котором значение TRUE или FALSE соответсвует наличию или отсутствию значения в соответствующей строке столбца missing
str(condition)
##  logi [1:2420] TRUE FALSE FALSE TRUE FALSE FALSE ...
mm[condition,] ## первый способ
## # A tibble: 271 x 12
##       id cause_of_death region_origin affected_nation~ missing  dead
##    <dbl> <chr>          <chr>         <chr>              <dbl> <dbl>
##  1     1 Presumed drow~ Middle East   Iraq                   1     1
##  2     6 Drowning       MENA          <NA>                   6     4
##  3    11 Drowning       <NA>          <NA>                   0     3
##  4    14 Presumed drow~ South Asia    Afghanistan            7     3
##  5    21 Drowning       <NA>          <NA>                  32     7
##  6    22 Drowning       Middle East/~ <NA>                   4     3
##  7    23 Drowning       Middle East/~ <NA>                   2    NA
##  8    30 Drowning       Middle East/~ <NA>                   1     3
##  9    36 Drowning       <NA>          <NA>                  13    43
## 10    37 Drowning       <NA>          <NA>                   1     1
## # ... with 261 more rows, and 6 more variables: incident_region <chr>,
## #   date <chr>, source <chr>, reliability <chr>, lat <dbl>, lon <dbl>
subset(mm, condition) ## второй способ
## # A tibble: 271 x 12
##       id cause_of_death region_origin affected_nation~ missing  dead
##    <dbl> <chr>          <chr>         <chr>              <dbl> <dbl>
##  1     1 Presumed drow~ Middle East   Iraq                   1     1
##  2     6 Drowning       MENA          <NA>                   6     4
##  3    11 Drowning       <NA>          <NA>                   0     3
##  4    14 Presumed drow~ South Asia    Afghanistan            7     3
##  5    21 Drowning       <NA>          <NA>                  32     7
##  6    22 Drowning       Middle East/~ <NA>                   4     3
##  7    23 Drowning       Middle East/~ <NA>                   2    NA
##  8    30 Drowning       Middle East/~ <NA>                   1     3
##  9    36 Drowning       <NA>          <NA>                  13    43
## 10    37 Drowning       <NA>          <NA>                   1     1
## # ... with 261 more rows, and 6 more variables: incident_region <chr>,
## #   date <chr>, source <chr>, reliability <chr>, lat <dbl>, lon <dbl>
filter(mm, condition) ## третий способ
## # A tibble: 271 x 12
##       id cause_of_death region_origin affected_nation~ missing  dead
##    <dbl> <chr>          <chr>         <chr>              <dbl> <dbl>
##  1     1 Presumed drow~ Middle East   Iraq                   1     1
##  2     6 Drowning       MENA          <NA>                   6     4
##  3    11 Drowning       <NA>          <NA>                   0     3
##  4    14 Presumed drow~ South Asia    Afghanistan            7     3
##  5    21 Drowning       <NA>          <NA>                  32     7
##  6    22 Drowning       Middle East/~ <NA>                   4     3
##  7    23 Drowning       Middle East/~ <NA>                   2    NA
##  8    30 Drowning       Middle East/~ <NA>                   1     3
##  9    36 Drowning       <NA>          <NA>                  13    43
## 10    37 Drowning       <NA>          <NA>                   1     1
## # ... with 261 more rows, and 6 more variables: incident_region <chr>,
## #   date <chr>, source <chr>, reliability <chr>, lat <dbl>, lon <dbl>
filter(mm, complete.cases(mm)) ## фильтрация строк с пропущенными значениями в хотя бы одном из столбцов
## # A tibble: 89 x 12
##       id cause_of_death region_origin affected_nation~ missing  dead
##    <dbl> <chr>          <chr>         <chr>              <dbl> <dbl>
##  1     1 Presumed drow~ Middle East   Iraq                   1     1
##  2    14 Presumed drow~ South Asia    Afghanistan            7     3
##  3    50 Drowning       Sub-Saharan ~ Senegal                1     1
##  4    56 Drowning       MENA          Syrian                 1     4
##  5    59 Drowning       Middle East   Iraqi                  3     4
##  6    70 Drowning       Middle East   Lebanese               2     7
##  7    86 Drowning       MENA          Syria                  5     1
##  8    97 Drowning       Middle East   Syrian Kurds          24     2
##  9   132 Drowning       Caribbean     Haiti                 19    21
## 10   133 Drowning       MENA          Syria, Iraq            4     7
## # ... with 79 more rows, and 6 more variables: incident_region <chr>,
## #   date <chr>, source <chr>, reliability <chr>, lat <dbl>, lon <dbl>

Однако, в результате такой “чистки” может остаться очень малая доля исходных данных, и нам будет нечего анализировать. В нашем случае после фильтрации всех пустых значений в выборке осталось всего 89 строк. Иногда наиболее предпочтительно заменить неизвестные значения на значения по умолчанию, например, “Unknown” или наиболее типичное (среднее или медиану). Это можно сделать несколькими способами:

mm[is.na(mm$cause_of_death),"cause_of_death"] <- "Unknown" ## первый способ: делаем выборку строк с отсутствующими значениями в столбце "cause_of_death" и для них задаем новое значение "Unknown"

mm[is.na(mm$affected_nationality),"affected_nationality"] <- "Unknown"

mm[is.na(mm$incident_region),"incident_region"] <- "Unknown"

median_missing <- median(mm$missing, na.rm = TRUE)
mm[is.na(mm$missing),"missing"] <- median_missing ## заменяем отсутствующие значения в столбце "missing" на медиану

mm[is.na(mm$dead),"dead"] <- 0 ## заменяем отсутствующие значения в столбце "dead" на 0

mm$region_origin <- ifelse(is.na(mm$region_origin),"Unknown", mm$region_origin) ## второй способ: для каждой строки проверяем условие и устанавливаем новое значение, если условие выполняется

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

mm <- filter(mm, !is.na(mm$date))
lapply(mm, FUN = function(x) {sum(is.na(x))})
## $id
## [1] 0
## 
## $cause_of_death
## [1] 0
## 
## $region_origin
## [1] 0
## 
## $affected_nationality
## [1] 0
## 
## $missing
## [1] 0
## 
## $dead
## [1] 0
## 
## $incident_region
## [1] 0
## 
## $date
## [1] 0
## 
## $source
## [1] 1
## 
## $reliability
## [1] 315
## 
## $lat
## [1] 4
## 
## $lon
## [1] 4

Как видим, в отредактированных столбцах не осталось отсутствующих значений.

Обратим внимание на столбец date. При загрузке данных тип столбца был автоматически распознан как character (строковый). Однако для работы удобнее, чтобы даты были распознаны как данные типа Date. Для этого можно воспользоваться функциями из пакета lubridate. В нашем случае даты записаны в формате “число-месяц-год”, поэтому используем функцию dmy():

library(lubridate)
## 
## Attaching package: 'lubridate'
## The following object is masked from 'package:base':
## 
##     date
str(mm$date)
##  chr [1:2411] "05/11/2015" "03/11/2015" "03/11/2015" "01/11/2015" ...
mm$date <- dmy(mm$date)
str(mm$date)
##  Date[1:2411], format: "2015-11-05" "2015-11-03" "2015-11-03" "2015-11-01" "2015-11-01" ...

Преобразование данных

В процессе анализа данных часто возникает потребность добавить в исходный набор данных столбцы с рассчитанными показателями. Например, для группировки случаев гибели мигрантов по годам было бы удобно создать столбец year, в котором был бы указан год, в котором произошел инцидент. Это можно сделать с помощью $ и функции year(), которая извлекает из даты год:

mm$year <- year(mm$date)
str(mm$year)
##  num [1:2411] 2015 2015 2015 2015 2015 ...

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

mm <- mutate(mm, dead_ratio=dead/mean(dead))
str(mm$dead_ratio)
##  num [1:2411] 0.22 0.22 0.22 0.88 0.88 ...

Исследование данных

Итак, мы выполнили предварительную подготовку данных, теперь можно приступить к анализу. Рассчитаем количество погибших за каждый год. Это можно сделать с помощью выборки нужных строк из набора данных и суммирования значений столбцов dead:

sum(mm[mm$year==2014,"dead"])
## [1] 1709
sum(mm[mm$year==2015,"dead"])
## [1] 4323
sum(mm[mm$year==2016,"dead"])
## [1] 3942
sum(mm[mm$year==2017,"dead"])
## [1] 981

Удобнее то же самое рассчитать с помощью функций group by() и summarize():

group_by(mm, year) %>%  ## группировка строк по столбцу year
  summarize(sum(dead)) ## суммирование количества погибших для каждой группы
## # A tibble: 4 x 2
##    year `sum(dead)`
##   <dbl>       <dbl>
## 1  2014        1709
## 2  2015        4323
## 3  2016        3942
## 4  2017         981

Аналогично рассчитаем количество погибших мигрантов по регионам происхождения, по национальности, по причинам смерти и по регионам, в которых произошли инциденты:

(by_ro <- group_by(mm, region_origin) %>%  
  summarize(dead=sum(dead))) 
## # A tibble: 17 x 2
##    region_origin             dead
##    <chr>                    <dbl>
##  1 Caribbean                  131
##  2 Central America            468
##  3 Central America & Mexico   936
##  4 East Asia                    5
##  5 Horn of Africa             854
##  6 Horn of Africa (P)        1539
##  7 MENA                      1106
##  8 Middle East                104
##  9 Middle East/ South Asia    198
## 10 Mixed                      447
## 11 North Africa                14
## 12 South America               22
## 13 South Asia                 201
## 14 South East Asia            762
## 15 Southern Europe              1
## 16 Sub-Saharan Africa        1378
## 17 Unknown                   2789
(by_an <- group_by(mm, affected_nationality) %>%  
  summarize(dead=sum(dead)))
## # A tibble: 219 x 2
##    affected_nationality                                                dead
##    <chr>                                                              <dbl>
##  1 'African'                                                              4
##  2 'Mostly African'                                                       3
##  3 'Sub-Saharan African'                                                 29
##  4 1 Honduran, 3 Mexican                                                  4
##  5 1 Nigerian, others unknown. Survivors all from Sub-Saharan Africa      3
##  6 1 Venezuelan, 1 unknown                                                2
##  7 13 Cuba, 1 Dominican Republic, 1 Colombia                              0
##  8 15 dead from Palestine. Missing are from Palestine, Syria, and Eg~    15
##  9 2 from Niger                                                          34
## 10 2 Senegal, 2 Guinea, 1 Ghana                                           5
## # ... with 209 more rows
(by_cd <- group_by(mm, cause_of_death) %>%  
  summarize(dead=sum(dead)))
## # A tibble: 290 x 2
##    cause_of_death                                dead
##    <chr>                                        <dbl>
##  1 AH1N1 influenza virus, while stuck at border     1
##  2 Asphyxiation                                    40
##  3 Asphyxiation (Silica sand inhalation)            1
##  4 Asphyxiation and crushing                       45
##  5 Assaulted by smugglers                           1
##  6 Attacked by hippopotamus                         4
##  7 Beat-up and killed                               1
##  8 Beat-up and thrown into river                    1
##  9 Beaten to death on train                         1
## 10 Beating/shot by traffickers                      4
## # ... with 280 more rows
(by_ir <- group_by(mm, incident_region) %>%  
  summarize(dead=sum(dead))) 
## # A tibble: 14 x 2
##    incident_region               dead
##    <chr>                        <dbl>
##  1 Caribbean                      111
##  2 Central America incl. Mexico   421
##  3 East Asia                        2
##  4 Europe                         230
##  5 Horn of Africa                 512
##  6 Mediterranean                 4826
##  7 Middle East                    190
##  8 North Africa                  2257
##  9 North America                    1
## 10 South America                   35
## 11 Southeast Asia                 807
## 12 Sub-Saharan Africa             426
## 13 U.S./Mexico Border            1136
## 14 Unknown                          1

Всего за рассматриваемый период погибло 1.095510^{4} мигрантов. Наибольшее количество погибших мигрантов происходило из региона Unknown (2789 человек). Наибольшая доля погибших мигрантов принадлежала национальности Unknown (5270 человек). Наиболее частая причина смерти мигрантов - Drowning (3917 человек). Наибольшее количество мигрантов погибло в регионе Mediterranean (4826 человек).

Выберем из исходного набора данных инциденты с количеством погибших выше среднего dead_ratio > 1 и отсортируем таблицу в порядке убывания количества погибших с помощью функции arrange():

(b_inc <- mm[mm$dead_ratio > 1, c("date","incident_region","dead","cause_of_death")]  %>%
  arrange(desc(dead))) 
## # A tibble: 409 x 4
##    date       incident_region     dead cause_of_death        
##    <date>     <chr>              <dbl> <chr>                 
##  1 2015-04-18 Mediterranean        750 Drowning, Asphyxiation
##  2 2014-03-22 Sub-Saharan Africa   251 Drowning              
##  3 2015-03-30 Southeast Asia       243 Mixed                 
##  4 2016-09-20 Mediterranean        204 Drowning              
##  5 2014-08-22 Mediterranean        170 Drowning              
##  6 2014-12-29 U.S./Mexico Border   147 Mixed                 
##  7 2015-12-30 U.S./Mexico Border   145 Mixed                 
##  8 2015-12-30 U.S./Mexico Border   139 Mixed                 
##  9 2016-07-21 Mediterranean        120 Presumed drowning     
## 10 2015-08-28 Mediterranean        111 Drowning              
## # ... with 399 more rows

Инцидент с наибольшим количеством погибших мигрантов произошел 2015-04-18 в регионе Mediterranean, когда по причине Drowning, Asphyxiation погибло 750 человек.

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

worldMap <- map_data("world")
ggplot(worldMap) + 
  geom_polygon(aes(long, lat, group = group), fill="white", color="grey") + 
  geom_point(data=mm, aes(lon, lat, color=dead), alpha=0.1) +
  coord_map(xlim=c(-120,120),ylim=c(-50,60))+
  labs(title="Количество погибших мигрантов по регионам", color="Количество\nпогибших\nмигрантов")
## Warning: Removed 4 rows containing missing values (geom_point).

Интересный пример исследования данного набора данных можно найти здесь.