Данный пример подготовлен на основе набора данных 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).
Интересный пример исследования данного набора данных можно найти здесь.