优化嵌套的ifelse语句和多个模式匹配

3
我正在继续使用一些动物收容所的数据进行数据清理练习。我的目标是将品种类别的数量缩小。
我将每个品种类别作为部分匹配模式与 outgoing$Single.Breed 数据框列进行匹配。因此,有些品种只会是 Chihuahua,但也可能是 Long Hair Chihuahua。(这就是我使用 grepl 的原因。)因此,任何包含品种类别的内容都将在另一列中由该类别表示。此外,我还需要添加猫的品种类别……这让代码变得更加凌乱。
下面的代码是我的“解决方案”,但它相当笨重。是否有更好、更流畅和/或更高效的方法来完成这项任务?
BreedCategories <- ifelse(outgoing$New.Type == "Dog",
           ifelse(grepl("Chihuahua",outgoing$Single.Breed, ignore.case = TRUE), "Chihuahua",
           ifelse(grepl("Pit Bull",outgoing$Single.Breed, ignore.case = TRUE), "Pit Bull",
           ifelse(grepl("Terrier",outgoing$Single.Breed, ignore.case = TRUE), "Terrier",
           ifelse(grepl("Shepherd",outgoing$Single.Breed, ignore.case = TRUE), "Shepherd",
           ifelse(grepl("Poodle",outgoing$Single.Breed, ignore.case = TRUE), "Poodle",
           ifelse(grepl("Labrador|Retriever",outgoing$Single.Breed, ignore.case = TRUE),"Labrador",
           "Other")))))),"Cat")

1
你需要包含足够的数据以使你的问题可重现,但请查看dplyr::case_when - alistaire
如果您只是想检查一个品种名称是否被列出,则不需要使用ifelse,再加上可以重现的示例以及您所需的输出会更理想。 - Nate
1个回答

4
创建一个数据框,将正则表达式与品种进行映射。
map <- data.frame(
    pattern=c(
        "Chihuahua", "Pit Bull", "Terrier", "Shepherd",
        "Poodle", "Labrador|Retriever", "Other"),
    isa=c(
        "Chihuahua", "Pit Bull", "Terrier", "Shepherd",
        "Poodle", "Labrador", "Other"),
    stringsAsFactors=FALSE)

和一些数据

outgoing <- data.frame(Single.Breed=c(map$isa, "Pit Bull Poodle", "Pug"),
                       stringsAsFactors=FALSE)

对于这个程序,使用 vapply()grepl() 来将每个模式与数据匹配;使用 grepl() 的意思是结果是一个矩阵,行对应于每个条目。

isa <- vapply(map$pattern, grepl, logical(nrow(outgoing)), outgoing$Single.Breed)
if (any(rowSums(isa) > 1))
    warning("ambiguous breeds: ", outgoing$Single.Breed[rowSums(isa) != 1])

使用max.col()访问每一行并检索最佳匹配(如果没有匹配,则为“Other”)。
outgoing$BreedCategory <- map$isa[max.col(isa, "last")]

以下是结果

> isa <- vapply(map$pattern, grepl, logical(nrow(outgoing)), outgoing$Single.Breed)
> if (any(rowSums(isa) > 1))
+     warning("ambiguous breeds: ", outgoing$Single.Breed[rowSums(isa) != 1])
Warning message:
ambiguous breeds: Pit Bull Poodle 
> outgoing$BreedCategory <- map$isa[max.col(isa, "last")]
> outgoing
     Single.Breed BreedCategory
1       Chihuahua     Chihuahua
2        Pit Bull      Pit Bull
3         Terrier       Terrier
4        Shepherd      Shepherd
5          Poodle        Poodle
6        Labrador      Labrador
7           Other         Other
8 Pit Bull Poodle        Poodle
9             Pug         Other

我猜这种方法更吸引人,因为它更清晰地将“数据”(正则表达式和输入品种)与“程序”(grepl()max.col())分开。
“其他”处理似乎有点脆弱 - 如果您忘记它应该是map的最后一个元素怎么办?一种可能性是创建一个指示变量来测试isa的行总和,并使用它来有条件地分配品种。
test = rowSums(isa)
outgoing$BreedCategory[test == 0] = "Other"
outgoing$BreedCategory[test == 1] = map$isa[max.col(isa)][test == 1]
outgoing$BreedCategory[test > 1] = "Mixed"

上述方法不太节省空间(矩阵将长度为n的数据转换为一个n x #正则表达式的矩阵),但对于1M行输入数据似乎能胜任任务。 dplyr::case_when() 似乎要求编写许多 grepl() 语句,这样容易出错。

我喜欢这个 - 你也可以使用 map$isa [max.col(isa, "first")] 来避免使用 apply 语句。 - thelatemail
@thelatemail 谢谢!我承认我不知道 max.col()(奇怪的是没有 max.row())。 - Martin Morgan
谢谢!这非常有帮助,完成了工作!这肯定是我将添加到未来使用的R工具箱中的另一个聪明策略!我同意“其他”相当脆弱。指示变量是解决它的好方法,尽管空间效率不高。幸运的是,我的数据集并不是特别庞大,所以如果需要,那个变量肯定是一个选项。 - kimbekaw

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接