¿Cómo hacer vlookup y rellenar (como en Excel) en R?
Frecuentes
Visto 222,021 equipos
94
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
9 Respuestas
133
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
34
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
11
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
8
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)
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)
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
6
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
5
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
5
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
4
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
2
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 r lookup or haz tu propia pregunta.
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