【问题标题】:Extract city names from large text with R使用 R 从大文本中提取城市名称
【发布时间】:2018-01-26 02:41:25
【问题描述】:

您好,我有一个有趣的问题。假设我有一个长字符,其中包括其他城市名称。

test<-"Ucsd Medical Center, San Diego, California, USA|Yale Cancer Center, New Haven, Connecticut, USA|Massachusetts General Hospital., Boston, Massachusetts, USA|Dana Farber Cancer Institute, Boston, Massachusetts, USA|Washington University, Saint Louis, Missouri, USA|Mount SInai Medical Center, New York, New York, USA|Memorial Sloan Kettering Cancer Center, New York, New York, USA|Carolinas Healthcare System, Charlotte, North Carolina, USA|University Hospitals Case Medical Center; Seidman Cancer Center, Cleveland, Ohio, USA|Vanderbilt University Medical Center, Nashville, Tennessee, USA|Seattle Cancer Care Alliance, Seattle, Washington, USA|National Cancer Center, Gyeonggi-do, Korea, Republic of|Seoul National University Hospital, Seoul, Korea, Republic of|Severance Hospital, Yonsei University Health System, Seoul, Korea, Republic of|Korea University Guro Hospital, Seoul, Korea, Republic of|Asan Medical Center., Seoul, Korea, Republic of|VU MEDISCH CENTRUM; Dept. of Medical Oncology"

我的目标是提取它的所有城市名称。我通过以下五个步骤实现了它。

   #replace | with ,
   test2<-str_replace_all(test, "[|]", ", ")

   # Remove punctuation from data
   test3<-gsub("[[:punct:]\n]","",test2)

   # Split data at word boundaries
   test4 <- strsplit(test3, " ")

   # Load data from package maps
   data(world.cities)

   # Match on cities in world.cities
   citiestest<-lapply(test4, function(x)x[which(x %in% world.cities$name)])

结果可能是正确的

citiestest
[[1]]
 [1] "San"        "Boston"     "Boston"     "Washington" "York"      
 [6] "York"       "Kettering"  "York"       "York"       "Charlotte" 
[11] "Carolina"   "Cleveland"  "Nashville"  "Seattle"    "Seattle"   
[16] "Washington" "Asan"      

但正如您所见,我无法处理名称由两个单词组成的城市(纽约、圣地亚哥等),因为它们是分开的。当然,手动修复这个问题不是一种选择,因为我的真实数据集非常大。

【问题讨论】:

  • 对命名实体提取和 nlp 进行一些查询(稍微使用关键字来调整结果)。如果您可以找到使用此类数据训练的模型,这是一个完美的用例。

标签: r extract


【解决方案1】:

一种完全不同的方法,可能或多或少有用,具体取决于手头的数据:将每个地址传递给地理编码 API,然后将城市从响应中提取出来。

library(tidyverse)

places <- data_frame(string = "Ucsd Medical Center, San Diego, California, USA|Yale Cancer Center, New Haven, Connecticut, USA|Massachusetts General Hospital., Boston, Massachusetts, USA|Dana Farber Cancer Institute, Boston, Massachusetts, USA|Washington University, Saint Louis, Missouri, USA|Mount SInai Medical Center, New York, New York, USA|Memorial Sloan Kettering Cancer Center, New York, New York, USA|Carolinas Healthcare System, Charlotte, North Carolina, USA|University Hospitals Case Medical Center; Seidman Cancer Center, Cleveland, Ohio, USA|Vanderbilt University Medical Center, Nashville, Tennessee, USA|Seattle Cancer Care Alliance, Seattle, Washington, USA|National Cancer Center, Gyeonggi-do, Korea, Republic of|Seoul National University Hospital, Seoul, Korea, Republic of|Severance Hospital, Yonsei University Health System, Seoul, Korea, Republic of|Korea University Guro Hospital, Seoul, Korea, Republic of|Asan Medical Center., Seoul, Korea, Republic of|VU MEDISCH CENTRUM; Dept. of Medical Oncology") %>% 
    separate_rows(string, sep = '\\|')

places <- places %>% 
    mutate(geodata = map(string, ~{Sys.sleep(1); ggmap::geocode(.x, output = 'all')}))

places <- places %>% 
    mutate(address_components = map(geodata, list('results', 1, 'address_components')),
           address_components = map(address_components, 
                                    ~as_data_frame(transpose(.x)) %>% 
                                        unnest(long_name, short_name)),
           city = map(address_components, unnest),
           city = map_chr(city, ~{
               l <- set_names(.x$long_name, .x$types); 
               coalesce(l['locality'], l['administrative_area_level_1'])
           }))

对比结果和原图,

places %>% select(city, string)
#> # A tibble: 17 x 2
#>    city       string                                                                               
#>    <chr>      <chr>                                                                                
#>  1 San Diego  Ucsd Medical Center, San Diego, California, USA                                      
#>  2 New Haven  Yale Cancer Center, New Haven, Connecticut, USA                                      
#>  3 Boston     Massachusetts General Hospital., Boston, Massachusetts, USA                          
#>  4 Boston     Dana Farber Cancer Institute, Boston, Massachusetts, USA                             
#>  5 St. Louis  Washington University, Saint Louis, Missouri, USA                                    
#>  6 New York   Mount SInai Medical Center, New York, New York, USA                                  
#>  7 New York   Memorial Sloan Kettering Cancer Center, New York, New York, USA                      
#>  8 Charlotte  Carolinas Healthcare System, Charlotte, North Carolina, USA                          
#>  9 Cleveland  University Hospitals Case Medical Center; Seidman Cancer Center, Cleveland, Ohio, USA
#> 10 Nashville  Vanderbilt University Medical Center, Nashville, Tennessee, USA                      
#> 11 Seattle    Seattle Cancer Care Alliance, Seattle, Washington, USA                               
#> 12 Goyang-si  National Cancer Center, Gyeonggi-do, Korea, Republic of                              
#> 13 서울특별시 Seoul National University Hospital, Seoul, Korea, Republic of                        
#> 14 Seoul      Severance Hospital, Yonsei University Health System, Seoul, Korea,  Republic of       
#> 15 Seoul      Korea University Guro Hospital, Seoul, Korea, Republic of                            
#> 16 Seoul      Asan Medical Center., Seoul, Korea, Republic of                                      
#> 17 Amsterdam  VU MEDISCH CENTRUM; Dept. of Medical Oncology   

...嗯,它并不完美。最大的问题是美国城市被归类为localities,但韩国被归类为administrative_area_level_1(在美国是州)。与其他韩国行不同,12 实际上有一个地方,它不是列出的城市(在响应中作为行政区域)。此外,第 13 行中的“首尔”莫名其妙地被翻译成了韩语。

好消息是“Saint Louis”已被缩写为“St. Louis”,这是一种更标准化的形式,最后一行已位于阿姆斯特丹。

扩展这种方法可能需要向 Google 支付一些使用其 API 的费用。

【讨论】:

  • Google 地理编码 API 有一个免费套餐(我已经使用了多年),所以如果 OP 想偶尔将它用于研究目的,那么他不需要支付任何费用。
【解决方案2】:

这是使用 strsplitsub 的基本 R 选项:

terms <- unlist(strsplit(test, "\\s*\\|\\s*"))
cities <- sapply(terms, function(x) gsub("[^,]+,\\s*([^,]+),.*", "\\1", x))
cities[1:3]

            Ucsd Medical Center, San Diego, California, USA 
                                                "San Diego" 
            Yale Cancer Center, New Haven, Connecticut, USA 
                                                "New Haven" 
Massachusetts General Hospital., Boston, Massachusetts, USA
                                                   "Boston"

Demo

【讨论】:

  • 不错的方法,但测试只包含我要检查的 25 个文本中的 1 个,并且由于某种原因,它不像 @Onyambu 解决方案那样在 for 循环中工作。
  • 它如何知道城市在哪里?它是在哪里定义的?
【解决方案3】:

另一种无需循环的方式

pat="(,.\\w+,)|(,.\\w+.\\w+,)"
gsub("(,\\s)|,","",regmatches(m<-strsplit(test,"\\|")[[1]],regexpr(pat,m)))

[1] "San Diego"   "New Haven"   "Boston"      "Boston"      "Saint Louis" "New York"    "New York"   
[8] "Charlotte"   "Cleveland"   "Nashville"   "Seattle"     "Gyeonggi-do" "Seoul"       "Seoul"      
[15] "Seoul"       "Seoul"    

此页面中给出的其他结果确实失败:例如,有一个名为Greonggi-do 的城镇,其他解决方案中没有给出。还有一些代码将整个字符串作为城镇

【讨论】:

  • 我的解决方案肯定找到了Greonggi-do的城镇。
  • yours 有,但在Severance Hospital, Yonsei University Health System, Seoul, Korea, Republic of 下,它给出了"Yonsei University Health System" 作为不正确的城市
  • 如果城市的位置不知道/是可变的,那么正则表达式不太适合这个。在这种情况下,OP 应该为这种边缘情况提供逻辑。
  • 嗯,这似乎是这里的挑战。然而,正如你所看到的那样,我给的那个确实找到了Seoul 作为城市。所以还是可以的
  • 有趣的方法。据我所知,它适用于单词的位置。你能解释一下你是如何在你的代码中定义这个位置的吗?
【解决方案4】:

我会做什么:

test2 <- str_replace_all(test, "[|]", ", ") #Same as you did

test3 <- unlist(strsplit(test2, split=", ")) #Turns string into a vector

check <- test3 %in% world.cities$name #Check if element vectors match list of city names

test3[check == TRUE] #Select vector elements that match list of city names

 [1] "San Diego"   "New Haven"   "Boston"      "Boston"      "Saint Louis" "New York"    "New York"    "New York"   
 [9] "New York"    "Charlotte"   "Cleveland"   "Nashville"   "Seattle"     "Washington" 

【讨论】:

    【解决方案5】:

    要扩展上述 @hrbrmstr 的评论,您可以使用斯坦福 CoreNLP 库对每个字符串进行命名实体识别 (NER)。对此类任务的最大警告是,大多数 NER 注释器仅将标记注释为“位置”或等价物,当城市与州和国家/地区混合时,这不是很有用。但是,除了通常的 NER 注释器之外,CoreNLP 确实包含一个额外的正则表达式 NER 注释器,可以将 NER 粒度增加到城市级别。

    在 R 中,您可以使用 coreNLP 包来运行注释器。它确实需要rJava,在某些情况下可能很难配置。您还需要下载实际的(相当大的)库,可以使用coreNLP::downloadCoreNLP 完成,如果您愿意,请将~/.Renviron 中的CORENLP_HOME 环境变量设置为安装路径。

    另请注意,这种方法相当缓慢且占用大量资源,因为它在 Java 中做了很多工作。

    library(tidyverse)
    library(coreNLP)
    
    # set which annotators to use
    writeLines('annotators = tokenize, ssplit, pos, lemma, ner, regexner\n', 'corenlp.properties')
    initCoreNLP(libLoc = Sys.getenv('CORENLP_HOME'), parameterFile = 'corenlp.properties')
    unlink('corenlp.properties')    # clean up
    
    places <- data_frame(string = "Ucsd Medical Center, San Diego, California, USA|Yale Cancer Center, New Haven, Connecticut, USA|Massachusetts General Hospital., Boston, Massachusetts, USA|Dana Farber Cancer Institute, Boston, Massachusetts, USA|Washington University, Saint Louis, Missouri, USA|Mount SInai Medical Center, New York, New York, USA|Memorial Sloan Kettering Cancer Center, New York, New York, USA|Carolinas Healthcare System, Charlotte, North Carolina, USA|University Hospitals Case Medical Center; Seidman Cancer Center, Cleveland, Ohio, USA|Vanderbilt University Medical Center, Nashville, Tennessee, USA|Seattle Cancer Care Alliance, Seattle, Washington, USA|National Cancer Center, Gyeonggi-do, Korea, Republic of|Seoul National University Hospital, Seoul, Korea, Republic of|Severance Hospital, Yonsei University Health System, Seoul, Korea, Republic of|Korea University Guro Hospital, Seoul, Korea, Republic of|Asan Medical Center., Seoul, Korea, Republic of|VU MEDISCH CENTRUM; Dept. of Medical Oncology") %>% 
        separate_rows(string, sep = '\\|')    # separate strings
    
    places_ner <- places %>% 
        mutate(annotations = map(string, annotateString),
               tokens = map(annotations, 'token'), 
               tokens = map(tokens, group_by, token_id = data.table::rleid(NER)), 
               city = map(tokens, filter, NER == 'CITY'), 
               city = map(city, summarise, city = paste(token, collapse = ' ')), 
               city = map_chr(city, ~if(nrow(.x) == 0) NA_character_ else .x$city))
    

    返回

    places_ner %>% select(city, string)
    #> # A tibble: 17 x 2
    #>    city      string                                                                               
    #>    <chr>     <chr>                                                                                
    #>  1 San Diego Ucsd Medical Center, San Diego, California, USA                                      
    #>  2 New Haven Yale Cancer Center, New Haven, Connecticut, USA                                      
    #>  3 Boston    Massachusetts General Hospital., Boston, Massachusetts, USA                          
    #>  4 Boston    Dana Farber Cancer Institute, Boston, Massachusetts, USA                             
    #>  5 NA        Washington University, Saint Louis, Missouri, USA                                    
    #>  6 NA        Mount SInai Medical Center, New York, New York, USA                                  
    #>  7 NA        Memorial Sloan Kettering Cancer Center, New York, New York, USA                      
    #>  8 Charlotte Carolinas Healthcare System, Charlotte, North Carolina, USA                          
    #>  9 Cleveland University Hospitals Case Medical Center; Seidman Cancer Center, Cleveland, Ohio, USA
    #> 10 Nashville Vanderbilt University Medical Center, Nashville, Tennessee, USA                      
    #> 11 Seattle   Seattle Cancer Care Alliance, Seattle, Washington, USA                               
    #> 12 NA        National Cancer Center, Gyeonggi-do, Korea, Republic of                              
    #> 13 Seoul     Seoul National University Hospital, Seoul, Korea, Republic of                        
    #> 14 Seoul     Severance Hospital, Yonsei University Health System, Seoul, Korea, Republic of       
    #> 15 Seoul     Korea University Guro Hospital, Seoul, Korea, Republic of                            
    #> 16 Seoul     Asan Medical Center., Seoul, Korea, Republic of                                      
    #> 17 NA        VU MEDISCH CENTRUM; Dept. of Medical Oncology   
    

    失败:

    • “纽约”被两次识别为州或省(“纽约市”将被识别为此类)。
    • “圣路易斯”被识别为一个人。 “St. Louis”在我的安装中被识别为位置,但 an online version of the same library 将原始位置识别为位置,因此这可能是版本问题。
    • “京畿道”未被识别,但“首尔”被识别。我不确定regexner 注释器的粒度如何,但鉴于(顾名思义)它由正则表达式工作,有一个大小/熟悉度阈值,在该阈值下它不包含正则表达式。 You can add your own regex to it 如果值得的话。

    cleanNLP package 还支持 Stanford CoreNLP(和其他几个后端),具有更易于使用的界面(设置仍然很困难),但据我所知不允许使用 regexner目前由于它如何初始化 CoreNLP。

    【讨论】:

    【解决方案6】:

    你可以使用tidytext提取bigram-->词-->相交得到共同部分

    library(tidyverse)
    libraty(tidytext)
    # city is a vector containing pre-defined city name
    t2 <- test %>% as_tibble() %>% 
    unnest_tokens(bigram,value,token = 'ngrams', n =2) %>% 
    separate(bigram,c('word1','word2'),remove = F) 
    
    city_get <- c(intersect(t2$bigram,city),intersect(t2$word1,city))%>%
                unique()
    

    【讨论】:

      猜你喜欢
      • 2016-07-15
      • 1970-01-01
      • 1970-01-01
      • 2017-06-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多