Простой и эффективный способ подмножества фрейма данных с использованием значений и имен в векторе - PullRequest
2 голосов
/ 17 июня 2020

Учитывая набор данных (скажем, сохраненный как фрейм данных) в форме:

> n <- 10   
> set.seed(123)
> ds.df <- data.frame(col1 = round(rnorm(n,2,4), digit = 1),
                    col2 = sample.int(2, n, replace = TRUE),
                    col3 = sample.int(n*10, n),
                    col4 = sample(letters, n, replace = TRUE))

существует ли простой и эффективный способ его подмножества, используя вектор значения, который определяет множественное равенство, которое подмножество должно уважение? Что-то вроде:

> subset_v <- c(col1 = -0.2, col4 = "i")
> ds.subset <- subset(ds.df, subset_v)
> ds.subset
  col1 col2 col3 col4
1 -0.2    1    9    i

, где функция subset(ds.df,subset_v) должна возвращать подмножество, которое уважает:

ds.df[ ds.df$col1 == subset_v["col1"] & ds.df$col2 == subset_v["col2"] & ds.df$col4 == subset_v["col4"], ]

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

Я сделал кое-что, что работает:

subset <- function(ds.df,subset_v){
    sub = rep(TRUE, nrow(ds.df))
    for(cn in names(subset_v)){
       sub=sub & (ds.df[,cn] == subset_v[[cn]])
    }
    ds.df[sub,]
}

Но мне кажется, что есть гораздо лучший и эффективный способ сделать это (возможно, удалить for loop как-то).

Ответы [ 3 ]

3 голосов
/ 17 июня 2020

Лично мне интересно, стоит ли использовать именованный вектор для подмножества фрейма данных, поскольку он может использоваться только для равенства =, а larger than и smaller than не могут быть выражены таким образом. Я бы рекомендовал использовать выражение в кавычках вместо именованного вектора (см. Подход ниже).

Однако я нашел способ tidyverse написать функцию с указанной функциональностью:

library(tidyverse)

set.seed(123)
n <- 10 

ds.df <- data.frame(col1 = round(rnorm(n,2,4), digit=1),
                   col2 = sample.int(2, n, replace=T),
                   col3 = sample.int(n*10, n),
                   col4 = sample(letters, n, replace=T))

new_filter <- function (data, expr) {
  exprs_ls <- purrr::imap(expr, ~ rlang::exprs(!! rlang::sym(.y) == !!.x))
  filter(data, !!! unname(unlist(exprs_ls)))
}

new_filter(ds.df, c(col1 = -0.2, col4 = "i"))
#>   col1 col2 col3 col4
#> 1 -0.2    1    9    i

Создано 17.06.2020 пакетом репекс (v0.3.0)

Ниже мой альтернативный подход . В base R вы можете использовать quote для цитирования выражения подмножества (вместо создания вектора), а затем вы можете использовать eval для его вычисления внутри subset.

n <- 10   

ds.df=data.frame(col1=round(rnorm(n,2,4),digit=1),
                 col2=sample.int(2,n,replace=T),
                 col3=sample.int(n*10,n),
                 col4=sample(letters,n,replace=T))


subset_v = quote(col1 > 2 & col3 > 40)

subset(ds.df, eval(subset_v))
#>    col1 col2 col3 col4
#> 1   6.6    1   93    m
#> 2   7.0    2   62    j
#> 4   3.9    1   94    t
#> 7   4.5    1   46    r
#> 8   2.8    2   98    h
#> 10  4.9    1   78    p

Создано 17.06.2020 с помощью пакета REPEX (v0.3.0)


Тот же подход, но с использованием dplyr filter

library(dplyr)

n <- 10 

ds.df = data.frame(col1 = round(rnorm(n,2,4), digit=1),
                   col2 = sample.int(2, n, replace=T),
                   col3 = sample.int(n*10, n),
                   col4 = sample(letters, n, replace=T))

filter_v = expr(col1 > 2 & col3 > 40)

filter(ds.df, !! filter_v)

#>   col1 col2 col3 col4
#> 1  3.3    1   70    a
#> 2  2.5    2   82    q
#> 3  3.6    1   51    z

Создано 17.06.2020 пакетом REPEX (v0.3.0)

2 голосов
/ 17 июня 2020

В data.table вы можете сделать это:

setDT(ds.df)
subset_v = list(col1=-3.3, col2=1, col4="e")
ds.df[as.list(subset_v), on = names(subset_v)]

#    col1 col2 col3 col4
# 1: -3.3    1   29    e

Воспроизводимые данные:

set.seed(20)
n <- 10   
ds.df <- data.frame(
  col1 = round(rnorm(n, 2, 4), digit = 1),
  col2 = sample.int(2, n, replace = TRUE),
  col3 = sample.int(n*10, n),
  col4 = sample(letters, n, replace = TRUE)
)
1 голос
/ 17 июня 2020

Я думаю, вы ищете merge:

subset <- function(ds.df,subset_v){
    filter = data.frame(as.list(subset_v))
    merge(ds.df,filter,by=names(filter),all=F)
}

Это работает как с data.frame, так и с data.table, а с data.table должно быть таким же, как ответ @ sindri_baldur , поэтому, если вы уже используете data.table, основное различие заключается в том, предпочитаете ли вы набирать merge(x,y,by=z,all=F) или x[y,on=z].

...