Seleccione dinámicamente columnas de marcos de datos usando $ y un valor de carácter

121

Tengo un vector de diferentes nombres de columna y quiero poder recorrer cada uno de ellos para extraer esa columna de un data.frame. Por ejemplo, considere el conjunto de datos mtcarsy algunos nombres de variables almacenados en un vector de caracteres cols. Cuando trato de seleccionar una variable del mtcarsuso de un subconjunto dinámico de cols, ninguno de estos funciona

cols <- c("mpg", "cyl", "am")
col <- cols[1]
col
# [1] "mpg"

mtcars$col
# NULL
mtcars$cols[1]
# NULL

¿Cómo puedo hacer que estos devuelvan los mismos valores que

mtcars$mpg

Además, ¿cómo puedo recorrer todas las columnas colspara obtener los valores en algún tipo de bucle?

for(x in seq_along(cols)) {
   value <- mtcars[ order(mtcars$cols[x]), ]
}
Samuel Song
fuente

Respuestas:

182

No puedes hacer ese tipo de subconjunto con $. En el código fuente ( R/src/main/subset.c) dice:

/ * El operador $ subset.
Necesitamos asegurarnos de evaluar solo el primer argumento.
El segundo será un símbolo que debe coincidir, no evaluar.
* /

¿Segundo argumento? ¡¿Qué?! Tienes que darte cuenta de que$ , como todo lo demás en R, (incluyendo, por ejemplo (, +, ^etc) es una función que toma argumentos y se evalúa. df$V1podría reescribirse como

`$`(df , V1)

o de hecho

`$`(df , "V1")

Pero...

`$`(df , paste0("V1") )

... por ejemplo, nunca funcionará, ni ninguna otra cosa que deba evaluarse primero en el segundo argumento. Solo puede pasar una cuerda que nunca evalúa.

En su lugar use [ (o [[si desea extraer solo una columna como vector).

Por ejemplo,

var <- "mpg"
#Doesn't work
mtcars$var
#These both work, but note that what they return is different
# the first is a vector, the second is a data.frame
mtcars[[var]]
mtcars[var]

Puede realizar el pedido sin bucles, utilizando do.callpara construir la llamada a order. A continuación, se muestra un ejemplo reproducible:

#  set seed for reproducibility
set.seed(123)
df <- data.frame( col1 = sample(5,10,repl=T) , col2 = sample(5,10,repl=T) , col3 = sample(5,10,repl=T) )

#  We want to sort by 'col3' then by 'col1'
sort_list <- c("col3","col1")

#  Use 'do.call' to call order. Seccond argument in do.call is a list of arguments
#  to pass to the first argument, in this case 'order'.
#  Since  a data.frame is really a list, we just subset the data.frame
#  according to the columns we want to sort in, in that order
df[ do.call( order , df[ , match( sort_list , names(df) ) ]  ) , ]

   col1 col2 col3
10    3    5    1
9     3    2    2
7     3    2    3
8     5    1    3
6     1    5    4
3     3    4    4
2     4    3    4
5     5    1    4
1     2    5    5
4     5    3    5
Simon O'Hanlon
fuente
¿Ha cambiado esta situación en los años posteriores?
Dunois
4

Si lo entiendo correctamente, tiene un vector que contiene nombres de variables y le gustaría recorrer cada nombre y ordenar su marco de datos por ellos. Si es así, este ejemplo debería ilustrarle una solución. El problema principal en el suyo (el ejemplo completo no está completo, así que no estoy seguro de qué más puede faltar) es que debería ser en order(Q1_R1000[,parameter[X]])lugar de order(Q1_R1000$parameter[X]), ya que el parámetro es un objeto externo que contiene un nombre de variable opuesto a una columna directa de su marco de datos (que cuando $sería apropiado).

set.seed(1)
dat <- data.frame(var1=round(rnorm(10)),
                   var2=round(rnorm(10)),
                   var3=round(rnorm(10)))
param <- paste0("var",1:3)
dat
#   var1 var2 var3
#1    -1    2    1
#2     0    0    1
#3    -1   -1    0
#4     2   -2   -2
#5     0    1    1
#6    -1    0    0
#7     0    0    0
#8     1    1   -1
#9     1    1    0
#10    0    1    0

for(p in rev(param)){
   dat <- dat[order(dat[,p]),]
 }
dat
#   var1 var2 var3
#3    -1   -1    0
#6    -1    0    0
#1    -1    2    1
#7     0    0    0
#2     0    0    1
#10    0    1    0
#5     0    1    1
#8     1    1   -1
#9     1    1    0
#4     2   -2   -2
David
fuente
4

El uso de dplyr proporciona una sintaxis sencilla para ordenar los marcos de datos

library(dplyr)
mtcars %>% arrange(gear, desc(mpg))

Puede resultar útil utilizar la versión NSE como se muestra aquí para permitir la creación dinámica de la lista de clasificación

sort_list <- c("gear", "desc(mpg)")
mtcars %>% arrange_(.dots = sort_list)
tiburón hombre
fuente
¿Qué significa NSE aquí?
discipulus
1
@discipulus evaluación no estándar; es para trabajar con expresiones retrasadas para construir dinámicamente el código con cadenas en lugar de codificarlo. Consulte aquí para obtener más información: cran.r-project.org/web/packages/lazyeval/vignettes/…
manotheshark
1

Otra solución es usar #get:

> cols <- c("cyl", "am")
> get(cols[1], mtcars)
 [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4
EJAg
fuente
0

Tuve un problema similar debido a algunos archivos CSV que tenían varios nombres para la misma columna.
Esta fue la solución:

Escribí una función para devolver el primer nombre de columna válido en una lista, luego usé eso ...

# Return the string name of the first name in names that is a column name in tbl
# else null
ChooseCorrectColumnName <- function(tbl, names) {
for(n in names) {
    if (n %in% colnames(tbl)) {
        return(n)
    }
}
return(null)
}

then...

cptcodefieldname = ChooseCorrectColumnName(file, c("CPT", "CPT.Code"))
icdcodefieldname = ChooseCorrectColumnName(file, c("ICD.10.CM.Code", "ICD10.Code"))

if (is.null(cptcodefieldname) || is.null(icdcodefieldname)) {
        print("Bad file column name")
}

# Here we use the hash table implementation where 
# we have a string key and list value so we need actual strings,
# not Factors
file[cptcodefieldname] = as.character(file[cptcodefieldname])
file[icdcodefieldname] = as.character(file[icdcodefieldname])
for (i in 1:length(file[cptcodefieldname])) {
    cpt_valid_icds[file[cptcodefieldname][i]] <<- unique(c(cpt_valid_icds[[file[cptcodefieldname][i]]], file[icdcodefieldname][i]))
}
R Keene
fuente
0

si desea seleccionar una columna con un nombre específico, simplemente haga

A=mtcars[,which(conames(mtcars)==cols[1])]
#and then
colnames(mtcars)[A]=cols[1]

puede ejecutarlo en bucle y también en forma inversa para agregar un nombre dinámico, por ejemplo, si A es un marco de datos y xyz es una columna que se llamará x, entonces me gusta esto

A$tmp=xyz
colnames(A)[colnames(A)=="tmp"]=x

de nuevo, esto también se puede agregar en bucle

makarand kulkarni
fuente
No sé por qué votado negativamente, pero funciona y la manera fácil en lugar de escribir funciones complicadas
makarand kulkarni
0
mtcars[do.call(order, mtcars[cols]), ]
Hong Ooi
fuente
-1

demasiado tarde ... pero supongo que tengo la respuesta -

Aquí está mi marco de datos de ejemplo de study.df:

   >study.df
   study   sample       collection_dt other_column
   1 DS-111 ES768098 2019-01-21:04:00:30         <NA>
   2 DS-111 ES768099 2018-12-20:08:00:30   some_value
   3 DS-111 ES768100                <NA>   some_value

Y entonces -

> ## Selecting Columns in an Given order
> ## Create ColNames vector as per your Preference
> 
> selectCols <- c('study','collection_dt','sample')
> 
> ## Select data from Study.df with help of selection vector
> selectCols %>% select(.data=study.df,.)
   study       collection_dt   sample
1 DS-111 2019-01-21:04:00:30 ES768098
2 DS-111 2018-12-20:08:00:30 ES768099
3 DS-111                <NA> ES768100
> 
ValaravausNegro
fuente