purrr
Допустим, вы забыли, что такое список:
## $m
## [1] "a"
##
## $n
## [1] 2 4
##
## $o
## [1] "blue" "green" "red"
## [1] "a"
## $m
## [1] "a"
## [1] 2 4
## [1] 4
Списки вне R обычно хранят в файлах типа .json
(по-английски говорят с ударением на первый слог, а вот по-русски прижился вариант с ударением на второй слог: джейсо́н) или .xml
(мы обсудим только .json
).
Самое важное, что нужно знать, про .json
, это то, что там могут хранится:
{"ключ":"значение"}
["значение_1","значение_2",...]
Чтение и запись .json
файлов осуществляется при помощи пакета jsonlite
(не входит в tidyverse
). Для примера скачаем датасет 30 героев Игры престолов, который Дж. Р. Р. Мартин достал с ресурса An API of Ice And Fire.
got_chars <- jsonlite::read_json("https://raw.githubusercontent.com/agricolamz/2019.12.21_EU_purrr/master/data/got_chars.json")
got_chars[1]
## [[1]]
## [[1]]$url
## [1] "https://www.anapioficeandfire.com/api/characters/1022"
##
## [[1]]$id
## [1] 1022
##
## [[1]]$name
## [1] "Theon Greyjoy"
##
## [[1]]$gender
## [1] "Male"
##
## [[1]]$culture
## [1] "Ironborn"
##
## [[1]]$born
## [1] "In 278 AC or 279 AC, at Pyke"
##
## [[1]]$died
## [1] ""
##
## [[1]]$alive
## [1] TRUE
##
## [[1]]$titles
## [[1]]$titles[[1]]
## [1] "Prince of Winterfell"
##
## [[1]]$titles[[2]]
## [1] "Captain of Sea Bitch"
##
## [[1]]$titles[[3]]
## [1] "Lord of the Iron Islands (by law of the green lands)"
##
##
## [[1]]$aliases
## [[1]]$aliases[[1]]
## [1] "Prince of Fools"
##
## [[1]]$aliases[[2]]
## [1] "Theon Turncloak"
##
## [[1]]$aliases[[3]]
## [1] "Reek"
##
## [[1]]$aliases[[4]]
## [1] "Theon Kinslayer"
##
##
## [[1]]$father
## [1] ""
##
## [[1]]$mother
## [1] ""
##
## [[1]]$spouse
## [1] ""
##
## [[1]]$allegiances
## [1] "House Greyjoy of Pyke"
##
## [[1]]$books
## [[1]]$books[[1]]
## [1] "A Game of Thrones"
##
## [[1]]$books[[2]]
## [1] "A Storm of Swords"
##
## [[1]]$books[[3]]
## [1] "A Feast for Crows"
##
##
## [[1]]$povBooks
## [[1]]$povBooks[[1]]
## [1] "A Clash of Kings"
##
## [[1]]$povBooks[[2]]
## [1] "A Dance with Dragons"
##
##
## [[1]]$tvSeries
## [[1]]$tvSeries[[1]]
## [1] "Season 1"
##
## [[1]]$tvSeries[[2]]
## [1] "Season 2"
##
## [[1]]$tvSeries[[3]]
## [1] "Season 3"
##
## [[1]]$tvSeries[[4]]
## [1] "Season 4"
##
## [[1]]$tvSeries[[5]]
## [1] "Season 5"
##
## [[1]]$tvSeries[[6]]
## [1] "Season 6"
##
##
## [[1]]$playedBy
## [1] "Alfie Allen"
Так как jsonlite
конфликтует в одной функции с tidyverse
, я не загружаю библиотеку полностью при помощи команды library(jsonlite)
, а обращаюсь к функциям пакета при помощи выражения jsonlite::...()
. Пакет jsonlite
позволяет записывать .json
файлы при помощи функции write_json()
:
Просмоторщик списков встроен в RStudio. Его можно увидеть, если ткнуть в объект в R или написать команду View(got_chars)
. Альтернативой может стать функция jsonedit()
из пакета listviewer
. Кроме того, что-то можно попробовать выяснить, используя функцию str()
.
## List of 1
## $ :List of 18
## ..$ url : chr "https://www.anapioficeandfire.com/api/characters/1022"
## ..$ id : int 1022
## ..$ name : chr "Theon Greyjoy"
## ..$ gender : chr "Male"
## ..$ culture : chr "Ironborn"
## ..$ born : chr "In 278 AC or 279 AC, at Pyke"
## ..$ died : chr ""
## ..$ alive : logi TRUE
## ..$ titles :List of 3
## .. ..$ : chr "Prince of Winterfell"
## .. ..$ : chr "Captain of Sea Bitch"
## .. ..$ : chr "Lord of the Iron Islands (by law of the green lands)"
## ..$ aliases :List of 4
## .. ..$ : chr "Prince of Fools"
## .. ..$ : chr "Theon Turncloak"
## .. ..$ : chr "Reek"
## .. ..$ : chr "Theon Kinslayer"
## ..$ father : chr ""
## ..$ mother : chr ""
## ..$ spouse : chr ""
## ..$ allegiances: chr "House Greyjoy of Pyke"
## ..$ books :List of 3
## .. ..$ : chr "A Game of Thrones"
## .. ..$ : chr "A Storm of Swords"
## .. ..$ : chr "A Feast for Crows"
## ..$ povBooks :List of 2
## .. ..$ : chr "A Clash of Kings"
## .. ..$ : chr "A Dance with Dragons"
## ..$ tvSeries :List of 6
## .. ..$ : chr "Season 1"
## .. ..$ : chr "Season 2"
## .. ..$ : chr "Season 3"
## .. ..$ : chr "Season 4"
## .. ..$ : chr "Season 5"
## .. ..$ : chr "Season 6"
## ..$ playedBy : chr "Alfie Allen"
## List of 1
## $ :List of 18
## ..$ url : chr "https://www.anapioficeandfire.com/api/characters/1022"
## ..$ id : int 1022
## ..$ name : chr "Theon Greyjoy"
## ..$ gender : chr "Male"
## ..$ culture : chr "Ironborn"
## ..$ born : chr "In 278 AC or 279 AC, at Pyke"
## ..$ died : chr ""
## ..$ alive : logi TRUE
## ..$ titles :List of 3
## ..$ aliases :List of 4
## ..$ father : chr ""
## ..$ mother : chr ""
## ..$ spouse : chr ""
## ..$ allegiances: chr "House Greyjoy of Pyke"
## ..$ books :List of 3
## ..$ povBooks :List of 2
## ..$ tvSeries :List of 6
## ..$ playedBy : chr "Alfie Allen"
purrr
В tidyverse
встроен пакет purrr
, который среди прочего позволяет работать со списками. Существует cheat sheet по purrr
. Ну и конечно полный туториал.
map()
и map...()
Можно выводить значения в списке на основе имени:
## [[1]]
## [1] "Theon Greyjoy"
##
## [[2]]
## [1] "Tyrion Lannister"
##
## [[3]]
## [1] "Victarion Greyjoy"
##
## [[4]]
## [1] "Will"
##
## [[5]]
## [1] "Areo Hotah"
##
## [[6]]
## [1] "Chett"
##
## [[7]]
## [1] "Cressen"
##
## [[8]]
## [1] "Arianne Martell"
##
## [[9]]
## [1] "Daenerys Targaryen"
##
## [[10]]
## [1] "Davos Seaworth"
##
## [[11]]
## [1] "Arya Stark"
##
## [[12]]
## [1] "Arys Oakheart"
##
## [[13]]
## [1] "Asha Greyjoy"
##
## [[14]]
## [1] "Barristan Selmy"
##
## [[15]]
## [1] "Varamyr"
##
## [[16]]
## [1] "Brandon Stark"
##
## [[17]]
## [1] "Brienne of Tarth"
##
## [[18]]
## [1] "Catelyn Stark"
##
## [[19]]
## [1] "Cersei Lannister"
##
## [[20]]
## [1] "Eddard Stark"
##
## [[21]]
## [1] "Jaime Lannister"
##
## [[22]]
## [1] "Jon Connington"
##
## [[23]]
## [1] "Jon Snow"
##
## [[24]]
## [1] "Aeron Greyjoy"
##
## [[25]]
## [1] "Kevan Lannister"
##
## [[26]]
## [1] "Melisandre"
##
## [[27]]
## [1] "Merrett Frey"
##
## [[28]]
## [1] "Quentyn Martell"
##
## [[29]]
## [1] "Samwell Tarly"
##
## [[30]]
## [1] "Sansa Stark"
Можно выводить значения в списке на основе индекса:
## [[1]]
## [1] "Theon Greyjoy"
##
## [[2]]
## [1] "Tyrion Lannister"
##
## [[3]]
## [1] "Victarion Greyjoy"
##
## [[4]]
## [1] "Will"
##
## [[5]]
## [1] "Areo Hotah"
##
## [[6]]
## [1] "Chett"
##
## [[7]]
## [1] "Cressen"
##
## [[8]]
## [1] "Arianne Martell"
##
## [[9]]
## [1] "Daenerys Targaryen"
##
## [[10]]
## [1] "Davos Seaworth"
##
## [[11]]
## [1] "Arya Stark"
##
## [[12]]
## [1] "Arys Oakheart"
##
## [[13]]
## [1] "Asha Greyjoy"
##
## [[14]]
## [1] "Barristan Selmy"
##
## [[15]]
## [1] "Varamyr"
##
## [[16]]
## [1] "Brandon Stark"
##
## [[17]]
## [1] "Brienne of Tarth"
##
## [[18]]
## [1] "Catelyn Stark"
##
## [[19]]
## [1] "Cersei Lannister"
##
## [[20]]
## [1] "Eddard Stark"
##
## [[21]]
## [1] "Jaime Lannister"
##
## [[22]]
## [1] "Jon Connington"
##
## [[23]]
## [1] "Jon Snow"
##
## [[24]]
## [1] "Aeron Greyjoy"
##
## [[25]]
## [1] "Kevan Lannister"
##
## [[26]]
## [1] "Melisandre"
##
## [[27]]
## [1] "Merrett Frey"
##
## [[28]]
## [1] "Quentyn Martell"
##
## [[29]]
## [1] "Samwell Tarly"
##
## [[30]]
## [1] "Sansa Stark"
Достаточно полезно знать о функции unlist()
, которая “убивает” все сложную структуру:
## [1] "Theon Greyjoy" "Tyrion Lannister" "Victarion Greyjoy"
## [4] "Will" "Areo Hotah" "Chett"
## [7] "Cressen" "Arianne Martell" "Daenerys Targaryen"
## [10] "Davos Seaworth" "Arya Stark" "Arys Oakheart"
## [13] "Asha Greyjoy" "Barristan Selmy" "Varamyr"
## [16] "Brandon Stark" "Brienne of Tarth" "Catelyn Stark"
## [19] "Cersei Lannister" "Eddard Stark" "Jaime Lannister"
## [22] "Jon Connington" "Jon Snow" "Aeron Greyjoy"
## [25] "Kevan Lannister" "Melisandre" "Merrett Frey"
## [28] "Quentyn Martell" "Samwell Tarly" "Sansa Stark"
Для этого есть и отдельные функции, которые позволяют превратить все в вектор заданного типа:
## [1] "Theon Greyjoy" "Tyrion Lannister" "Victarion Greyjoy"
## [4] "Will" "Areo Hotah" "Chett"
## [7] "Cressen" "Arianne Martell" "Daenerys Targaryen"
## [10] "Davos Seaworth" "Arya Stark" "Arys Oakheart"
## [13] "Asha Greyjoy" "Barristan Selmy" "Varamyr"
## [16] "Brandon Stark" "Brienne of Tarth" "Catelyn Stark"
## [19] "Cersei Lannister" "Eddard Stark" "Jaime Lannister"
## [22] "Jon Connington" "Jon Snow" "Aeron Greyjoy"
## [25] "Kevan Lannister" "Melisandre" "Merrett Frey"
## [28] "Quentyn Martell" "Samwell Tarly" "Sansa Stark"
## [1] 1022 1052 1074 1109 1166 1267 1295 130 1303 1319 148 149 150 168 2066
## [16] 208 216 232 238 339 529 576 583 60 605 743 751 844 954 957
## [1] TRUE TRUE TRUE FALSE TRUE FALSE FALSE TRUE TRUE TRUE TRUE FALSE
## [13] TRUE TRUE FALSE TRUE TRUE FALSE TRUE FALSE TRUE TRUE TRUE TRUE
## [25] FALSE TRUE FALSE FALSE TRUE TRUE
Можно даже создать новый датафрейм:
got_chars %>%
tibble(name = map_chr(., "name"), # точка обозначает, в какой аргумент должно все пайпиться
id = map_dbl(., "id"),
alive = map_lgl(., "alive"))
Также существует способ создание датафреймов на основе вектора значений при помощи функции enframe()
:
Скачайте частотный словарь русского языка [Шаров, Ляшевская 2011] (он храниться в .tsv
), разбейте столбец lemma
на буквы при помощи функции str_split(dict$lemma, "")
, а на основе полученного списка постройте график, на котором изображено, сколько раз встретилась какая буква:
tidyverse
включили? ➡ library(tidyverse)
. .tsv
? Почему словарь скачивается как один столбец? ➡ .tsv
— это как .csv
, только в качестве разделителя не запятая, а табуляция (\t
). Попробуйте использовать другой разделитель или функцию read_tsv()
из пакета readr
(входит в tidyverse
). read_tsv()
из пакета readr
(входит в tidyverse
) — она сама борется с кодировками. unlist()
. ggplot2
требует на вход датафрейм, так что его можно сделать при помощи чего угодно. Я использовал enframe()
. tolower("ВАША СТРОКА")
из базого R, или str_to_lower()
из пакета stringr
(входит в tidyverse
). filter()
из пакета dplyr
. А дальше уже вопрос техники: можно использовать констуркцию value != "-"
. А можно убить двух зайцев одним выстрелом и использовать регулярку: filter(str_detect(value, "\\w"))
. count()
. Можно было вообще не считать и использовать geom_bar()
, но тогда не получиться отсортировать по возрастанию. geom_
использовать? ➡ geom_col()
. Можно еще использовать geom_bar()
на неагрегированных данных, но тогда не удастся отсортировать. coord_flip()
к объекту ggplot
. fct_reorder()
из пакета forcats
(входит в tidyverse
) уже в ggplot()
: data %>% ggplot(aes(x = fct_reorder(value, n), y = n)) + ...
. В датасете, собранный Adithya Ganesh, хранится информация об играках Английской Премьер-лиги. На основании этих данных (лежат в папке data
) постройте график 30 игроков, которые забили больше всего голов и раскрасьте на основании их футбольного клуба.
Если в списке есть логические выражения, их можно использовать как фильтры. Например, вот так мы оставим только живых героев:
## [1] "Theon Greyjoy" "Tyrion Lannister" "Victarion Greyjoy"
## [4] "Areo Hotah" "Arianne Martell" "Daenerys Targaryen"
## [7] "Davos Seaworth" "Arya Stark" "Asha Greyjoy"
## [10] "Barristan Selmy" "Brandon Stark" "Brienne of Tarth"
## [13] "Cersei Lannister" "Jaime Lannister" "Jon Connington"
## [16] "Jon Snow" "Aeron Greyjoy" "Melisandre"
## [19] "Samwell Tarly" "Sansa Stark"
А так – только мертвых:
## [1] "Will" "Chett" "Cressen" "Arys Oakheart"
## [5] "Varamyr" "Catelyn Stark" "Eddard Stark" "Kevan Lannister"
## [9] "Merrett Frey" "Quentyn Martell"
Также есть особый фильтр head_while()
и tail_while()
, который выделяет единицы (с начала и конца) до первого FALSE
.
## [1] "Theon Greyjoy" "Tyrion Lannister" "Victarion Greyjoy"
## [1] "Samwell Tarly" "Sansa Stark"
Если все еще не понятно, взгляните на нашу таблицу:
got_chars %>%
tibble(name = map_chr(., "name"), # точка обозначает, в какой аргумент должно все пайпиться
alive = map_lgl(., "alive"))
Рассмотрим простой пример:
## $a
## [1] 1 2 3
##
## $b
## [1] "a" "b"
##
## $c
## $c[[1]]
## [1] "z"
##
## $c[[2]]
## [1] 8 9
Как уже говорилось выше, функция unlist()
линеаризует списки, превращая их в векторы:
## a1 a2 a3 b1 b2 c1 c2 c3
## "1" "2" "3" "a" "b" "z" "8" "9"
Получился поименнованный вектор, если этого недостаточно, можно уничтожить и имена при помощи функции unname()
:
## [1] "1" "2" "3" "a" "b" "z" "8" "9"
Функция flatten
позволяет уничтожить лишь один, верхний,, уровень иерархии:
## [[1]]
## [1] 1
##
## [[2]]
## [1] 2
##
## [[3]]
## [1] 3
##
## [[4]]
## [1] "a"
##
## [[5]]
## [1] "b"
##
## [[6]]
## [1] "z"
##
## [[7]]
## [1] 8 9
Как видно из этого примера, первый элемент списка my_l
превратился в три первых элемента списка, а вот подсписок c = list("z", c(8, 9))
превратился в элемент z
и вектор c(8, 9)
.
Существуют также функции append()
и prepend()
, которые позволяют добавлять новый посписок после (или до) старого:
## [[1]]
## [1] 1 2
##
## [[2]]
## [1] "a" "b" "c"
##
## $new
## [1] TRUE FALSE
## $new
## [1] TRUE FALSE
##
## [[2]]
## [1] 1 2
##
## [[3]]
## [1] "a" "b" "c"
Существует также функции cross()
и cross2()
, которые позволяют получить уникальные комбинации объектов из двух списков:
## List of 6
## $ :List of 2
## ..$ : chr "a"
## ..$ : int 1
## $ :List of 2
## ..$ : chr "b"
## ..$ : int 1
## $ :List of 2
## ..$ : chr "a"
## ..$ : int 2
## $ :List of 2
## ..$ : chr "b"
## ..$ : int 2
## $ :List of 2
## ..$ : chr "a"
## ..$ : int 3
## $ :List of 2
## ..$ : chr "b"
## ..$ : int 3
## List of 6
## $ :List of 2
## ..$ : chr "a"
## ..$ : int 1
## $ :List of 2
## ..$ : chr "b"
## ..$ : int 1
## $ :List of 2
## ..$ : chr "a"
## ..$ : int 2
## $ :List of 2
## ..$ : chr "b"
## ..$ : int 2
## $ :List of 2
## ..$ : chr "a"
## ..$ : int 3
## $ :List of 2
## ..$ : chr "b"
## ..$ : int 3
map()
На самом деле все функции map()
— являются циклами со следующей структурой:
map(you_object, your_function)
Это значит, что мы можем использовать map()
В датасетах содержаться информация об объектах, выданных библиотекой Сиэтла 100 и более раз (исходные данные доступны здесь). Датасет состоит из кучи .csv
файлов с 5 переменными:
id
– идентификационный номер объектаtype
– тип объекта (bk
– книга, bknh
– другая категория с книгами, cas
– кассеты, cd
– CD, dvd
– DVD, kit
– комплект (я сам пока не разобрался что там…), vhs
– видеокассеты VHS)name
– названиеn
– сколько раз взяли в том или иному годуyear
– годДанные лежат вот здесь:
Давайте считаем их все:
links <- str_c("https://raw.githubusercontent.com/agricolamz/2019.12.21_EU_purrr/master/data/seattle_public_library_", 2005:2019, ".csv")
df <- map(links, read_csv)
str(df, max.level = 1)
## List of 15
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 3864 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 11197 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 12141 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 15526 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 16821 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 15046 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 13793 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 13091 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 15092 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 14197 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 12313 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 10705 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 10485 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 9593 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
## $ :Classes 'spec_tbl_df', 'tbl_df', 'tbl' and 'data.frame': 6631 obs. of 5 variables:
## ..- attr(*, "spec")=
## .. .. cols(
## .. .. id = col_double(),
## .. .. type = col_character(),
## .. .. name = col_character(),
## .. .. n = col_double(),
## .. .. year = col_double()
## .. .. )
Можно даже соединить их в один датафрейм:
df_2 %>%
group_by(type, name) %>%
summarise(sum_n = sum(n)) %>%
ungroup() %>%
arrange(-sum_n) %>%
slice(1:100) %>%
ggplot(aes(fct_reorder(str_trunc(name, 40), sum_n), sum_n, fill = type))+
geom_col()+
coord_flip()+
labs(x = "", y = "",
title = "Most popular physical item checkouts from Seattle Public Library (2005-2019)",
caption = "data from https://data.seattle.gov/dataset/Checkouts-by-Title-Physical-Items-/3h5r-qv5w")