У меня есть таблица данных с полем для подсчета людей из каждой страны в формате «XYZ: #», где XYZ — это код страны, а # количество.
Вот пример 4 строк из таблицы данных:
dt <-data.table(event = c("Event 1", "Event 2", "Event 3", "Event 4"),
desc = c("Desc1", "Desc1", "Desc2", "Desc3"),
countries = c("USA:433, MEX:132, GRC:58, GBR:50, IRL:35, ITA:20",
"ESP:42, DEU:40, ITA:20, SLE:7",
"GBR:78, JAM:63, USA:30, AUT:18, GHA:5",
"NLD:53, GBR:21, CHN:20"))
Поле «страны» может содержать любое количество записей, единственная структура которых состоит в том, что оно начинается от наиболее многочисленных к наименее многочисленным. Мне нужно разобрать это в таблицу данных для анализа комбинаций событий и описаний. Поэтому мне нужна таблица, показывающая количество по коду страны, со строками в том же порядке, чтобы я мог объединить ее обратно с полями события и описания.
Мое решение до сих пор очень неэффективно, оно перебирает каждую строку, а затем анализирует столбец стран сначала для разделителя запятой, а затем для двоеточия. Вот что у меня есть до сих пор:
# Reference data for ISO 3-alpha country codes as character vector
nat_codes <- read.csv("countries_codes.csv")[[3]]
# Create empty data table
master_dt <- data.table(matrix(nrow = 0, ncol = length(nat_codes)+1))
names(master_dt) <- c(sort(nat_codes), "Unknown")
for (i in 1:nrow(dt)){
# split each string by the "," separator
row_component <- strsplit(dt$countries, ",")[[i]]
country_codes <- c()
numbers <- c()
# Loop through each component and separate country from count
for (j in 1:length(row_component)){
my_split <- strsplit(row_component[[j]], ":")[[1]]
# Extract the country code, add to vector
country_codes[j] <- gsub("\\s+", "", my_split[1])
# Extract the number
numbers[j] <- as.numeric(my_split[2])
}
# Re-assemble country codes and numbers as a data table and bind
row_dt <- data.table(country = country_codes, number = numbers)
row_dt <- transpose(row_dt)
names(row_dt) <- as.character(row_dt[1,])
row_dt <- row_dt[-1,]
master_dt <- rbindlist(list(master_dt, row_dt),
fill = TRUE,
use.names = TRUE)
}
# remove rows with no entries
master_dt <- Filter(function(x)!all(is.na(x)), master_dt)
Результат, который я получаю от этого, правильный (хотя мне нужно было бы преобразовать тип), но этот подход слишком неэффективен для масштабирования.
USA MEX GRC GBR IRL ITA ESP DEU SLE JAM AUT GHA NLD CHN
1: 433 132 58 50 35 20 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
2: <NA> <NA> <NA> <NA> <NA> 20 42 40 7 <NA> <NA> <NA> <NA> <NA>
3: 30 <NA> <NA> 78 <NA> <NA> <NA> <NA> <NA> 63 18 5 <NA> <NA>
4: <NA> <NA> <NA> 21 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 53 20
Мне нужно использовать это для сотен тысяч записей, но это довольно медленно для тестового набора около 1000 строк. Каковы самые простые победы здесь для повышения производительности? Предпочтительны базовые и data.table решения.
Существует вторичная проблема получения кодов стран, которых нет в справочных данных, но я могу назначить их неизвестным или игнорировать их. Спасибо.
Спасибо, я не точно указал! Я обновил описание проблемы вверху и включил результат своего решения, который является ожидаемым результатом, просто этот подход очень неэффективен и не совсем осуществим для моего большего набора данных.
Здесь я использовал другой пакет reshape2
, чтобы помочь с трансформацией.
Петля все еще есть, но значительно короче. Используя dt
, как указано в вашем коде, я сделал следующее.
dt_expand <- data.table()
for(i in 1:nrow(dt)){
row_component <- unlist(strsplit(dt[i, countries], ","))
TempDT <- data.table(event = rep(dt[i, event], length(row_component)),
countries = gsub(":.*", "", row_component),
counts = gsub(".*:", "", row_component))
dt_expand <- rbind(dt_expand, TempDT)
}
dt_recast <- dcast(dt_expand, event ~ countries, value.var = "counts")
Я думаю, это должно решить вашу проблему. Если вы хотите, чтобы ожидаемый результат был немного другим, дайте мне знать, и я могу отредактировать код.
library(dplyr)
dt %>%
mutate(countries = strsplit(countries, ", ")) %>%
unnest(countries) %>%
separate(countries, into = c("countries", "number"), sep = ":")
Хорошо, я думаю, что вам нужен tyr для отдельной(), а не только dplyr (если только моя версия не устарела). Обычно я избегаю tidyverse, но +1 за читабельность.
Использование sapply
и списка unique
счетчика всех стран.
library(data.table)
countr <- unique(sub(":.*", "", unlist(strsplit(dt$countries, ", "))))
data.table(t(sapply(strsplit(dt$countries, ", "), function(co)
sapply(countr, function(x){
res <- sub(".*:", "", grep(x, co, value=T))
ifelse(identical(res, character(0)), NA, res)}))))
USA MEX GRC GBR IRL ITA ESP DEU SLE JAM AUT GHA NLD CHN
1: 433 132 58 50 35 20 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA>
2: <NA> <NA> <NA> <NA> <NA> 20 42 40 7 <NA> <NA> <NA> <NA> <NA>
3: 30 <NA> <NA> 78 <NA> <NA> <NA> <NA> <NA> 63 18 5 <NA> <NA>
4: <NA> <NA> <NA> 21 <NA> <NA> <NA> <NA> <NA> <NA> <NA> <NA> 53 20
Для сравнения скорости немного измененная версия, работающая в индексированном data.table
и использующая lapply
countr <- unique(sub(":.*", "", unlist(strsplit(dt$countries, ", "))))
setNames(data.table(t(dt[, lapply(strsplit(countries, ", "), function(co)
lapply(countr, function(x){
res <- sub(".*:", "", grep(x, co, value=T))
ifelse(identical(res, character(0)), NA, res)}))])), countr)
USA MEX GRC GBR IRL ITA ESP DEU SLE JAM AUT GHA NLD CHN
1: 433 132 58 50 35 20 NA NA NA NA NA NA NA NA
2: NA NA NA NA NA 20 42 40 7 NA NA NA NA NA
3: 30 NA NA 78 NA NA NA NA NA 63 18 5 NA NA
4: NA NA NA 21 NA NA NA NA NA NA NA NA 53 20
Спасибо, это здорово. И решает проблему отсутствия кодов стран в справочном списке. Я бы подумал, что работа с индексированной таблицей данных будет быстрее, но microbenchmark считает, что первая быстрее, это ожидается?
@stackmeistergeneral Интересно, я бы предположил, что второй тоже быстрее, но, возможно, поскольку он не работает в пределах исходной структуры данных, перестановка делает его медленнее.
Не могли бы вы также опубликовать ожидаемый результат?