¿Cómo hacer vlookup y rellenar (como en Excel) en R?

Tengo un conjunto de datos de 105000 filas y 30 columnas. Tengo una variable categórica que me gustaría asignarle un número. En Excel, probablemente haría algo con VLOOKUP y llenar

¿Cómo haría para hacer lo mismo en R?

Esencialmente, lo que tengo es un HouseType variable, y necesito calcular la HouseTypeNo. Aquí hay algunos datos de muestra:

HouseType HouseTypeNo
Semi            1
Single          2
Row             3
Single          2
Apartment       4
Apartment       4
Row             3

preguntado el 08 de marzo de 13 a las 21:03

9 Respuestas

Si entiendo su pregunta correctamente, aquí hay cuatro métodos para hacer el equivalente de Excel VLOOKUP y rellene usando R:

# load sample data from Q
hous <- read.table(header = TRUE, 
                   stringsAsFactors = FALSE, 
text="HouseType HouseTypeNo
Semi            1
Single          2
Row             3
Single          2
Apartment       4
Apartment       4
Row             3")

# create a toy large table with a 'HouseType' column 
# but no 'HouseTypeNo' column (yet)
largetable <- data.frame(HouseType = as.character(sample(unique(hous$HouseType), 1000, replace = TRUE)), stringsAsFactors = FALSE)

# create a lookup table to get the numbers to fill
# the large table
lookup <- unique(hous)
  HouseType HouseTypeNo
1      Semi           1
2    Single           2
3       Row           3
5 Apartment           4

Aquí hay cuatro métodos para llenar el HouseTypeNo en el capítulo respecto a la largetable utilizando los valores de la lookup mesa:

Primero con merge conforme:

# 1. using base 
base1 <- (merge(lookup, largetable, by = 'HouseType'))

Un segundo método con vectores con nombre en la base:

# 2. using base and a named vector
housenames <- as.numeric(1:length(unique(hous$HouseType)))
names(housenames) <- unique(hous$HouseType)

base2 <- data.frame(HouseType = largetable$HouseType,
                    HouseTypeNo = (housenames[largetable$HouseType]))

Tercero, usando el plyr paquete:

# 3. using the plyr package
library(plyr)
plyr1 <- join(largetable, lookup, by = "HouseType")

Cuarto, usando el sqldf paquete

# 4. using the sqldf package
library(sqldf)
sqldf1 <- sqldf("SELECT largetable.HouseType, lookup.HouseTypeNo
FROM largetable
INNER JOIN lookup
ON largetable.HouseType = lookup.HouseType")

Si es posible que algunos tipos de casa en largetable no existen en lookup entonces se usaría una combinación izquierda:

sqldf("select * from largetable left join lookup using (HouseType)")

También se necesitarían los cambios correspondientes a las otras soluciones.

¿Es eso lo que querías hacer? Déjame saber qué método te gusta y agregaré un comentario.

Respondido el 11 de junio de 15 a las 05:06

Me di cuenta de que esto es bastante tarde, pero gracias por su ayuda. Probé tanto el primer como el segundo método. Ambos funcionaron bien. De nuevo, ¡gracias por responder a la pregunta! - user2142810

De nada. Si respondió a su pregunta, puede indicarlo haciendo clic en la marca debajo de las flechas en la parte superior izquierda. Eso será útil para otros que tengan la misma pregunta. - paquet

Creo que la solución n. ° 2 funciona solo porque en su ejemplo los valores únicos están en orden creciente (= el primer nombre único es 1, el segundo nombre único es 2 y así sucesivamente). Si agrega 'hous', digamos en el segunda fila 'HousType=ECII' , HousTypeNo='17' la búsqueda sale mal. - ECII

@ECII, continúe y agregue su respuesta que ilustre el problema y muestre su solución: paquet

Buena publicación. ¡Gracias por compartir! #4 funcionó muy bien para mi aplicación... uniéndose a través de dos tablas muy grandes de 400 MB. - Nathaniel Payne

Creo que también puedes usar match():

largetable$HouseTypeNo <- with(lookup,
                     HouseTypeNo[match(largetable$HouseType,
                                       HouseType)])

Esto todavía funciona si codigo el orden de lookup.

Respondido 20 Oct 15, 21:10

tambien me gusta usar qdapTools::lookup o operador binario abreviado %l%. Funciona de manera idéntica a un vlookup de Excel, pero acepta argumentos de nombre opuestos a los números de columna.

## Replicate Ben's data:
hous <- structure(list(HouseType = c("Semi", "Single", "Row", "Single", 
    "Apartment", "Apartment", "Row"), HouseTypeNo = c(1L, 2L, 3L, 
    2L, 4L, 4L, 3L)), .Names = c("HouseType", "HouseTypeNo"), 
    class = "data.frame", row.names = c(NA, -7L))


largetable <- data.frame(HouseType = as.character(sample(unique(hous$HouseType), 
    1000, replace = TRUE)), stringsAsFactors = FALSE)


## It's this simple:
library(qdapTools)
largetable[, 1] %l% hous

respondido 03 nov., 14:20

El cartel no preguntó acerca de buscar valores si exact=FALSE, pero estoy agregando esto como una respuesta para mi propia referencia y posiblemente para otros.

Si está buscando valores categóricos, use las otras respuestas.

De Excel vlookup también le permite hacer coincidir coincidencias aproximadas para valores numéricos con el cuarto argumento (4) match=TRUE. pienso match=TRUE como buscar valores en un termómetro. El valor predeterminado es FALSO, que es perfecto para valores categóricos.

Si desea hacer coincidir aproximadamente (realizar una búsqueda), R tiene una función llamada findInterval, que (como su nombre lo indica) encontrará el intervalo/bin que contiene su valor numérico continuo.

Sin embargo, digamos que quieres findInterval para varios valores. Puede escribir un bucle o usar una función de aplicación. Sin embargo, he encontrado que es más eficiente adoptar un enfoque vectorizado de bricolaje.

Digamos que tiene una cuadrícula de valores indexados por x e y:

grid <- list(x = c(-87.727, -87.723, -87.719, -87.715, -87.711), 
             y = c(41.836, 41.839, 41.843, 41.847, 41.851), 
             z = (matrix(data = c(-3.428, -3.722, -3.061, -2.554, -2.362, 
                                  -3.034, -3.925, -3.639, -3.357, -3.283, 
                                  -0.152, -1.688, -2.765, -3.084, -2.742, 
                                   1.973,  1.193, -0.354, -1.682, -1.803, 
                                   0.998,  2.863,  3.224,  1.541, -0.044), 
                         nrow = 5, ncol = 5)))

y tiene algunos valores que desea buscar por x e y:

df <- data.frame(x = c(-87.723, -87.712, -87.726, -87.719, -87.722, -87.722), 
                 y = c(41.84, 41.842, 41.844, 41.849, 41.838, 41.842), 
                 id = c("a", "b", "c", "d", "e", "f")

Aquí está el ejemplo visualizado:

contour(grid)
points(df$x, df$y, pch=df$id, col="blue", cex=1.2)

Dibujo de contorno

Puede encontrar los intervalos x y los intervalos y con este tipo de fórmula:

xrng <- range(grid$x)
xbins <- length(grid$x) -1
yrng <- range(grid$y)
ybins <- length(grid$y) -1
df$ix <- trunc( (df$x - min(xrng)) / diff(xrng) * (xbins)) + 1
df$iy <- trunc( (df$y - min(yrng)) / diff(yrng) * (ybins)) + 1

Podría ir un paso más allá y realizar una interpolación (simplista) en los valores z en grid Me gusta esto:

df$z <- with(df, (grid$z[cbind(ix, iy)] + 
                      grid$z[cbind(ix + 1, iy)] +
                      grid$z[cbind(ix, iy + 1)] + 
                      grid$z[cbind(ix + 1, iy + 1)]) / 4)

Lo que te da estos valores:

contour(grid, xlim = range(c(grid$x, df$x)), ylim = range(c(grid$y, df$y)))
points(df$x, df$y, pch=df$id, col="blue", cex=1.2)
text(df$x + .001, df$y, lab=round(df$z, 2), col="blue", cex=1)

Gráfico de contorno con valores

df
#         x      y id ix iy        z
# 1 -87.723 41.840  a  2  2 -3.00425
# 2 -87.712 41.842  b  4  2 -3.11650
# 3 -87.726 41.844  c  1  3  0.33150
# 4 -87.719 41.849  d  3  4  0.68225
# 6 -87.722 41.838  e  2  1 -3.58675
# 7 -87.722 41.842  f  2  2 -3.00425

Tenga en cuenta que ix e iy también podrían haberse encontrado con un bucle usando findInterval, por ejemplo, aquí hay un ejemplo para la segunda fila

findInterval(df$x[2], grid$x)
# 4
findInterval(df$y[2], grid$y)
# 2

que coincide ix y iy in df[2]

Nota al pie: (1) El cuarto argumento de vlookup se llamaba anteriormente "coincidencia", pero después de que introdujeron la cinta, se le cambió el nombre a "[range_lookup]".

Respondido el 11 de diciembre de 15 a las 18:12

Solución #2 de la respuesta de @Ben no es reproducible en otros ejemplos más genéricos. Sucede que da la búsqueda correcta en el ejemplo porque el único HouseType in houses aparecen en orden creciente. Prueba esto:

hous <- read.table(header = TRUE,   stringsAsFactors = FALSE,   text="HouseType HouseTypeNo
  Semi            1
  ECIIsHome       17
  Single          2
  Row             3
  Single          2
  Apartment       4
  Apartment       4
  Row             3")

largetable <- data.frame(HouseType = as.character(sample(unique(hous$HouseType), 1000, replace = TRUE)), stringsAsFactors = FALSE)
lookup <- unique(hous)

La solución de Bens #2 da

housenames <- as.numeric(1:length(unique(hous$HouseType)))
names(housenames) <- unique(hous$HouseType)
base2 <- data.frame(HouseType = largetable$HouseType,
                    HouseTypeNo = (housenames[largetable$HouseType]))

que cuando

unique(base2$HouseTypeNo[ base2$HouseType=="ECIIsHome" ])
[1] 2

cuando la respuesta correcta es 17 de la tabla de búsqueda

La forma correcta de hacerlo es

 hous <- read.table(header = TRUE,   stringsAsFactors = FALSE,   text="HouseType HouseTypeNo
      Semi            1
      ECIIsHome       17
      Single          2
      Row             3
      Single          2
      Apartment       4
      Apartment       4
      Row             3")

largetable <- data.frame(HouseType = as.character(sample(unique(hous$HouseType), 1000, replace = TRUE)), stringsAsFactors = FALSE)

housenames <- tapply(hous$HouseTypeNo, hous$HouseType, unique)
base2 <- data.frame(HouseType = largetable$HouseType,
  HouseTypeNo = (housenames[largetable$HouseType]))

Ahora las búsquedas se realizan correctamente.

unique(base2$HouseTypeNo[ base2$HouseType=="ECIIsHome" ])
ECIIsHome 
       17

Traté de editar la respuesta de Ben, pero se rechazó por razones que no puedo entender.

Respondido el 09 de diciembre de 13 a las 19:12

Empezando con:

houses <- read.table(text="Semi            1
Single          2
Row             3
Single          2
Apartment       4
Apartment       4
Row             3",col.names=c("HouseType","HouseTypeNo"))

... puedes usar

as.numeric(factor(houses$HouseType))

... para dar un número único para cada tipo de casa. Puedes ver el resultado aquí:

> houses2 <- data.frame(houses,as.numeric(factor(houses$HouseType)))
> houses2
  HouseType HouseTypeNo as.numeric.factor.houses.HouseType..
1      Semi           1                                    3
2    Single           2                                    4
3       Row           3                                    2
4    Single           2                                    4
5 Apartment           4                                    1
6 Apartment           4                                    1
7       Row           3                                    2

... entonces terminas con diferentes números en las filas (porque los factores están ordenados alfabéticamente) pero el mismo patrón.

(EDITAR: el texto restante en esta respuesta es realmente redundante. Se me ocurrió verificar y resultó que read.table() ya había convertido houses$HouseType en un factor cuando se leyó en el marco de datos en primer lugar).

Sin embargo, es posible que sea mejor simplemente convertir HouseType en un factor, lo que le brindaría los mismos beneficios que HouseTypeNo, pero sería más fácil de interpretar porque los tipos de casas se nombran en lugar de numerarse, por ejemplo:

> houses3 <- houses
> houses3$HouseType <- factor(houses3$HouseType)
> houses3
  HouseType HouseTypeNo
1      Semi           1
2    Single           2
3       Row           3
4    Single           2
5 Apartment           4
6 Apartment           4
7       Row           3
> levels(houses3$HouseType)
[1] "Apartment" "Row"       "Semi"      "Single"  

respondido 08 mar '13, 21:03

Podrías usar mapvalues() del paquete plyr.

Datos iniciales:

dat <- data.frame(HouseType = c("Semi", "Single", "Row", "Single", "Apartment", "Apartment", "Row"))

> dat
  HouseType
1      Semi
2    Single
3       Row
4    Single
5 Apartment
6 Apartment
7       Row

Tabla de búsqueda/cruce de peatones:

lookup <- data.frame(type_text = c("Semi", "Single", "Row", "Apartment"), type_num = c(1, 2, 3, 4))
> lookup
  type_text type_num
1      Semi        1
2    Single        2
3       Row        3
4 Apartment        4

Crea la nueva variable:

dat$house_type_num <- plyr::mapvalues(dat$HouseType, from = lookup$type_text, to = lookup$type_num)

O para reemplazos simples, puede omitir la creación de una tabla de búsqueda larga y hacerlo directamente en un solo paso:

dat$house_type_num <- plyr::mapvalues(dat$HouseType,
                                      from = c("Semi", "Single", "Row", "Apartment"),
                                      to = c(1, 2, 3, 4))

Resultado:

> dat
  HouseType house_type_num
1      Semi              1
2    Single              2
3       Row              3
4    Single              2
5 Apartment              4
6 Apartment              4
7       Row              3

Respondido 20 Oct 15, 21:10

Usar merge es diferente de la búsqueda en Excel, ya que tiene el potencial de duplicar (multiplicar) sus datos si la restricción de clave principal no se aplica en la tabla de búsqueda o reduce la cantidad de registros si no está usando all.x = T.

Para asegurarse de no meterse en problemas con eso y buscar de forma segura, sugiero dos estrategias.

El primero es verificar una cantidad de filas duplicadas en la clave de búsqueda:

safeLookup <- function(data, lookup, by, select = setdiff(colnames(lookup), by)) {
  # Merges data to lookup making sure that the number of rows does not change.
  stopifnot(sum(duplicated(lookup[, by])) == 0)
  res <- merge(data, lookup[, c(by, select)], by = by, all.x = T)
  return (res)
}

Esto lo obligará a desduplicar el conjunto de datos de búsqueda antes de usarlo:

baseSafe <- safeLookup(largetable, house.ids, by = "HouseType")
# Error: sum(duplicated(lookup[, by])) == 0 is not TRUE 

baseSafe<- safeLookup(largetable, unique(house.ids), by = "HouseType")
head(baseSafe)
# HouseType HouseTypeNo
# 1 Apartment           4
# 2 Apartment           4
# ...

La segunda opción es reproducir el comportamiento de Excel tomando el primer valor coincidente del conjunto de datos de búsqueda:

firstLookup <- function(data, lookup, by, select = setdiff(colnames(lookup), by)) {
  # Merges data to lookup using first row per unique combination in by.
  unique.lookup <- lookup[!duplicated(lookup[, by]), ]
  res <- merge(data, unique.lookup[, c(by, select)], by = by, all.x = T)
  return (res)
}

baseFirst <- firstLookup(largetable, house.ids, by = "HouseType")

Estas funciones son ligeramente diferentes de lookup ya que agregan varias columnas.

contestado el 22 de mayo de 16 a las 00:05

LA lookup El paquete se puede utilizar aquí:

library(lookup)
# reference data
hous <- data.frame(HouseType=c("Semi","Single","Row","Single","Apartment","Apartment","Row"),
                   HouseTypeNo=c(1,2,3,2,4,4,3))
# new large data with HouseType but no HouseTypeNo
largetable <- data.frame(HouseType = sample(unique(hous$HouseType), 1000, replace = TRUE))

# vector approach
largetable$num1 <- lookup(largetable$HouseType, hous$HouseType, hous$HouseTypeNo)
# dataframe approach
largetable$num2 <- vlookup(largetable$HouseType, hous, "HouseType", "HouseTypeNo")

head(largetable)
#   HouseType num1 num2
# 1      Semi    1    1
# 2      Semi    1    1
# 3 Apartment    4    4
# 4      Semi    1    1
# 5    Single    2    2
# 6    Single    2    2

Respondido 14 Abr '21, 17:04

Esta solución es la más cercana a la implementación de Excel. - Soumya Boral

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.