2017-02-14 5 views
2

У меня есть кадр данных, который содержит столбец беспорядочных строк. Каждая беспорядочная строка включает имя одной страны где-то в ней. Вот версия игрушка:От строки до регулярного выражения до новой строки

df <- data.frame(string = c("Russia is cool (2015) ", 
          "I like - China", 
          "Stuff happens in North Korea"), 
       stringsAsFactors = FALSE) 

Благодаря countrycode пакет, у меня также есть второй набор данных, который включает в себя два полезных столбца: один с regexs для названий стран (regex), а другой с именем ассоциированной страны (country.name) , Мы можем загрузить этот набор данных, как это:

library(countrycode) 
data(countrycode_data) 

Я хотел бы написать код, который использует регулярные выражения в countrycode_data$regex, чтобы определить название страны, в каждой строке df$string; ассоциирует это регулярное выражение с соответствующим именем страны в countrycode_data$country.name; и, наконец, записывает это имя в соответствующую позицию в новом столбце, df$country. После выполнения этой операции ТПО, df будет выглядеть следующим образом:

     string        country 
1  Russia is cool (2015)      Russian Federation 
2    I like - China         China 
3 Stuff happens in North Korea Korea, Democratic People's Republic of 

Я не могу достаточно обернуть вокруг головы, как это сделать. Я попытался использовать различные комбинации grepl, which, tolower и %in%, но я получаю неправильное направление или размеры (или оба).

+0

Я не вижу в 'regex' столбец в' кадра данных countrycode_data' ... EDIT, фигу, я Думаю, я нашел его, называемый 'country.name.en.regex'? – rosscova

+0

Соответствующий столбец в 'countrycode_data' должен просто называться' regex'. Соответствующий столбец с собственными именами - 'country.name'. – ulfelder

+0

возможно что-то подобное может помочь: http://stackoverflow.com/questions/21165256/r-merge-data-frames-allow-inexact-id-matching-eg-with-additional-characters – Bulat

ответ

1

Я бы с цикл в этом но зацикливается над строками countrycode_data data.frame, так как у этого есть только 200 строк, тогда как исходные данные реального мира могут быть на порядок больше.

Из-за длинных имен, извлечь два столбца данных код страны:

patt <- countrycode_data$country.name.en.regex[!is.na(countrycode_data$country.name.en.regex)] 
name <- countrycode_data$country.name.en[!is.na(countrycode_data$country.name.en.regex)] 

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

for(i in seq_along(patt)) { 
    df$country[grepl(patt[i], df$string, ignore.case=TRUE, perl=TRUE)] <- name[i] 
} 

Как уже отмечалось, Северная Корея не соответствует регулярному выражению, указанному в данных кода страны.

+1

Элегантный, спасибо. (И, как это бывает, я действительно получаю желаемый результат для «Северной Кореи» тоже.) – ulfelder

+1

Да, хорошее мышление. Я думал о том же, используя 'stringi', что-то вроде' which (sapply (countrycode_data $ country.name.en.regex, stringi :: stri_detect_regex, str = tolower (df $ string)), arr.ind = TRUE) '(где 'col' - индекс строки в' countrycode_data $ country.name.en') –

+0

@DavidArenburg также является хорошей альтернативой. В конце концов вы должны сделать один (и только один) цикл каким-то образом. stringi может значительно повысить соответствие регулярному выражению (и, конечно же, может быть принято и в моем подходе) –

1

Это рабочее решение, но я ссылаюсь на разные имена столбцов в фрейме countrycode_data, потому что они возникают по-другому в моей системе. Я также прибегал к нескольким звонкам *apply, что, вероятно, не идеально. Я уверен, что вы могли бы векторизовать некоторые из них, я просто не уверен, как себя.

matches <- sapply(df$string, function(x) { 

    # find matches by running all regex strings (maybe cound be vectorised?) 
    find.match <- lapply(countrycode_data$country.name.en.regex, grep, x = x, ignore.case = TRUE, perl = TRUE) 

    # note down which patterns came up with a match 
    matches <- which(sapply(find.match, length) > 0) 

    # now cull the matches list down to only those with a match 
    find.match <- find.match[ sapply(find.match, length) > 0 ] 

    # get rid of NA matches (not sure why these come up) 
    matches <- matches[ sapply(find.match, is.na) == FALSE ] 

    # now only return the value (reference to the match) if there is one (otherwise we get empty returns) 
    ifelse(length(matches) == 0, NA_integer_, matches) 
}) 

# now use the vector of references to match up country names 
df$country <- countrycode_data$country.name.en[ matches ] 

> df 
         string   country 
1  Russia is cool (2015) Russian Federation 
2    I like - China    China 
3 Stuff happens in North Korea    <NA> 

ПРИМЕЧАНИЕ: третья строка «Материал происходит в Северной Корее», должны соответствовать грести 128 в countrycode_data наборе, но это не так. Я думаю, причина в том, что регулярное выражение (^(?=.*democrat|people|north|d.*p.*.r).*\bkorea|dprk|korea.*(d.*p.*r)), похоже, указывает, что «север» должен быть началом строки. Я не очень хорошо разбираюсь в regex, но я считаю, что именно это задает ^. Посмотрите, что происходит с тремя текстовыми строками ниже:

grepl("^(?=.*democrat|people|north|d.*p.*.r).*\\bkorea|dprk|korea.*(d.*p.*r)", 
     c("korea", "north korea", "aaa north korea"), 
     perl = TRUE, ignore.case = TRUE) 
# [1] FALSE TRUE FALSE 
0

Здесь можно решение с перекрестными Join (который будет раздутие данных)

library(countrycode) 
data(countrycode_data) 

library(data.table) 
df <- data.table(string = c("Russia is cool (2015) ", 
          "I like - China", 
          "Stuff happens in North Korea"), 
       stringsAsFactors = FALSE) 

# adding dummy for full cross-join merge 
df$dummy <- 0L 
country.dt <- data.table(countrycode_data[, c("country.name.en", "country.name.en.regex")]) 
country.dt$dummy <- 0L 

# merging original data to countries to get all possible combinations 
res.dt <- merge(df, country.dt, by ="dummy", all = TRUE, allow.cartesian = TRUE) 

# there are cases with NA regex 
res.dt <- res.dt[!is.na(country.name.en.regex)] 

# find matches 
res.dt[, match := grepl(country.name.en.regex, string, perl = T, ignore.case = T), by = 1:nrow(res.dt)] 

# filter out matches 
res.dt <- res.dt[match == TRUE, .(string, country.name.en)] 
res.dt 

#     string country.name.en 
# 1: Russia is cool (2015) Russian Federation 
# 2:   I like - China    China 
+1

Зачем перекрестно присоединяться, если вы в итоге просто выполняете операции с помощью строки? Могла бы просто сделать ИСО «просто». –

+0

Согласен, в этом конкретном случае это не очень хорошее решение, так как ожидаемое количество матчей невелико. Но для подобных задач может оказаться полезным другое. – Bulat

2

Это как раз цель для пакета countrycode, поэтому нет причин перекодировать это самостоятельно. Просто используйте его вот так ...

library(countrycode) 
df <- data.frame(string = c("Russia is cool (2015) ", "I like - China", 
          "Stuff happens in North Korea"), stringsAsFactors = FALSE) 

df$country.name <- countrycode(df$string, 'country.name', 'country.name') 

именно в этом случае, это будет не найти однозначный матч за «Материал происходит в Северной Корее», но это на самом деле проблема с регулярными выражениями для Северной Кореи и Южной Кореи (я открыл вопрос, что здесь https://github.com/vincentarelbundock/countrycode/issues/139). В противном случае то, что вы хотите сделать, должно работать в принципе.

(примечание стороны специально @ulfelder: новая версия countrycode была выпущена только на CRAN, v0.19 Имена столбцов изменились немного, так как мы добавили новые языки, так country.name теперь country.name.en и regex теперь country.name.en.regex.)

1

Я поддерживаю код страны. @ cj-yetman дал правильный ответ. Конкретная проблема Северной Кореи, с которой вы столкнулись, теперь исправлена ​​в версии для версии countrycode на Github.

Вы можете использовать COUNTRYCODE непосредственно преобразовать предложения в названия стран или коды:

> library(devtools) 
> install_github('vincentarelbundock/countrycode') 
> library(countrycode) 
> df <- data.frame(string = c("Russia is cool (2015) ", 
+        "I like - China", 
+        "Stuff happens in North Korea"), 
+     stringsAsFactors = FALSE) 
> df$iso3c = countrycode(df$string, 'country.name', 'country.name') 
> df 
         string         iso3c 
1  Russia is cool (2015)      Russian Federation 
2    I like - China         China 
3 Stuff happens in North Korea Democratic People's Republic of Korea 
+1

Спасибо, @Vincent! В каком-то смысле я рад, что получил более общий ответ, прежде чем получить специфичный для «countrycode» код, потому что это может повториться для меня снова в ситуациях, когда пакет не решает проблему. – ulfelder

+0

есть эффективный способ использовать «countrycode» для поиска нескольких названий стран в одной строке? Например, если у меня есть строка «Доклады Генерального секретаря по Судану и Южному Судану», и я хочу вернуть строку типа «Судан, Южный Судан»? Я знаю, как сделать рушится. Это возвращает больше, чем одно совпадение, которое преклоняет меня. – ulfelder

+1

Не из коробки с кодом страны, но если вы посмотрите на внутренний код, пакет уже отслеживает несколько совпадений. Вы можете использовать один и тот же код и перехватить '' destination_list''. См. Здесь: https://github.com/vincentarelbundock/countrycode/blob/master/R/countrycode.R#L123 – Vincent

 Смежные вопросы

  • Нет связанных вопросов^_^