J'ai un ensemble de données d'environ 105 000 lignes et 30 colonnes. J'ai une variable catégorique que j'aimerais attribuer à un nombre. Dans Excel, je ferais probablement quelque chose avec VLOOKUP
et remplirait.
Comment ferais-je pour faire la même chose dans R
?
Essentiellement, ce que j’ai est une variable HouseType
, et j’ai besoin de calculer le HouseTypeNo
. Voici quelques exemples de données:
HouseType HouseTypeNo
Semi 1
Single 2
Row 3
Single 2
Apartment 4
Apartment 4
Row 3
Si je comprends bien votre question, voici quatre méthodes pour faire l’équivalent de VLOOKUP
d’Excel et remplir avec 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
Voici quatre méthodes pour remplir la HouseTypeNo
dans la largetable
en utilisant les valeurs de la table lookup
:
D'abord avec merge
dans la base:
# 1. using base
base1 <- (merge(lookup, largetable, by = 'HouseType'))
Une seconde méthode avec des vecteurs nommés en 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]))
Troisièmement, en utilisant le paquetage plyr
:
# 3. using the plyr package
library(plyr)
plyr1 <- join(largetable, lookup, by = "HouseType")
Quatrièmement, en utilisant le paquetage sqldf
# 4. using the sqldf package
library(sqldf)
sqldf1 <- sqldf("SELECT largetable.HouseType, lookup.HouseTypeNo
FROM largetable
INNER JOIN lookup
ON largetable.HouseType = lookup.HouseType")
S'il est possible que certains types de maisons dans largetable
n'existent pas dans lookup
, une jointure gauche serait utilisée:
sqldf("select * from largetable left join lookup using (HouseType)")
Des modifications correspondantes des autres solutions seraient également nécessaires.
Est-ce ce que tu voulais faire? Faites-moi savoir quelle méthode vous aimez et je vais ajouter un commentaire.
Je pense que vous pouvez aussi utiliser match()
:
largetable$HouseTypeNo <- with(lookup,
HouseTypeNo[match(largetable$HouseType,
HouseType)])
Cela fonctionne toujours si je brouille l'ordre de lookup
.
J'aime aussi utiliser qdapTools::lookup
ou l'opérateur binaire abrégé %l%
. Il fonctionne de manière identique à un vlookup Excel, mais accepte les arguments de nom opposés aux numéros de colonne.
## 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
Solution # 2 de la réponse de @Ben n'est pas reproductible dans d'autres exemples plus génériques. Il se trouve que la recherche est correcte dans l'exemple car l'unique HouseType
dans houses
apparaît par ordre croissant. Essaye ça:
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)
Bens solution n ° 2 donne
housenames <- as.numeric(1:length(unique(hous$HouseType)))
names(housenames) <- unique(hous$HouseType)
base2 <- data.frame(HouseType = largetable$HouseType,
HouseTypeNo = (housenames[largetable$HouseType]))
qui quand
unique(base2$HouseTypeNo[ base2$HouseType=="ECIIsHome" ])
[1] 2
lorsque la bonne réponse est 17 dans la table de recherche
La bonne façon de le faire est
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]))
Maintenant, les recherches sont effectuées correctement
unique(base2$HouseTypeNo[ base2$HouseType=="ECIIsHome" ])
ECIIsHome
17
J'ai essayé de modifier la réponse à Bens mais celle-ci est rejetée pour des raisons que je ne comprends pas.
L'affiche ne demandait pas de rechercher des valeurs si exact=FALSE
, mais j'ajoute ceci comme réponse pour ma propre référence et éventuellement pour d'autres.
Si vous recherchez des valeurs catégoriques, utilisez les autres réponses.
Le vlookup
d'Excel vous permet également de faire correspondre approximativement les valeurs numériques avec le 4ème argument (1) match=TRUE
. Je pense à match=TRUE
comme à la recherche de valeurs sur un thermomètre. La valeur par défaut est FALSE, ce qui est parfait pour les valeurs catégorielles.
Si vous souhaitez faire correspondre approximativement (effectuer une recherche), R a une fonction appelée findInterval
, qui (comme son nom l’indique) trouvera l’intervalle/bin qui contient votre valeur numérique continue.
Cependant, supposons que vous souhaitiez findInterval
pour plusieurs valeurs. Vous pouvez écrire une boucle ou utiliser une fonction apply. Cependant, j'ai trouvé plus efficace d'adopter une approche vectorielle bricolée.
Disons que vous avez une grille de valeurs indexées par x et 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)))
et vous avez des valeurs que vous voulez rechercher par x et 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")
Voici l'exemple visualisé:
contour(grid)
points(df$x, df$y, pch=df$id, col="blue", cex=1.2)
Vous pouvez trouver les intervalles x et les intervalles y avec ce type de formule:
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
Vous pourriez aller un peu plus loin et effectuer une interpolation (simpliste) sur les valeurs z dans grid
comme ceci:
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)
Ce qui vous donne ces valeurs:
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
Notez que ix, et iy auraient également pu être trouvés avec une boucle utilisant findInterval
, par ex. voici un exemple pour la deuxième rangée
findInterval(df$x[2], grid$x)
# 4
findInterval(df$y[2], grid$y)
# 2
Qui correspond à ix
et iy
dans df[2]
Footnote: (1) Le quatrième argument de vlookup s'appelait auparavant "match", mais après l'introduction du ruban, il a été renommé "[range_lookup]".
Commençant par:
houses <- read.table(text="Semi 1
Single 2
Row 3
Single 2
Apartment 4
Apartment 4
Row 3",col.names=c("HouseType","HouseTypeNo"))
... vous pouvez utiliser
as.numeric(factor(houses$HouseType))
... pour donner un numéro unique pour chaque type de maison. Vous pouvez voir le résultat ici:
> 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
... donc vous vous retrouvez avec des nombres différents sur les lignes (car les facteurs sont classés par ordre alphabétique) mais le même motif.
(EDIT: le texte restant de cette réponse est en fait redondant. Je me suis alors mis à vérifier et il est apparu que read.table()
avait déjà transformé les maisons $ HouseType en un facteur lorsqu’il a été lu dans le cadre de données en premier lieu) .
Cependant, il peut être préférable de convertir HouseType en facteur, ce qui vous donnerait les mêmes avantages que HouseTypeNo, mais serait plus facile à interpréter car les types de maison sont nommés plutôt que numérotés, par exemple:
> 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"
Vous pouvez utiliser mapvalues()
à partir du paquet plyr.
Données initiales:
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
Table de concordance:
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
Créer la nouvelle variable:
dat$house_type_num <- plyr::mapvalues(dat$HouseType, from = lookup$type_text, to = lookup$type_num)
Ou, pour les remplacements simples, vous pouvez ignorer la création d'une longue table de recherche et le faire directement en une seule étape:
dat$house_type_num <- plyr::mapvalues(dat$HouseType,
from = c("Semi", "Single", "Row", "Apartment"),
to = c(1, 2, 3, 4))
Résultat:
> 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
L'utilisation de merge
diffère de la recherche dans Excel car elle risque de dupliquer (multiplier) vos données si la contrainte de clé primaire n'est pas appliquée dans la table de recherche ou de réduire le nombre d'enregistrements si vous n'utilisez pas all.x = T
.
Pour vous assurer de ne pas avoir de problème avec cela et de chercher en toute sécurité, je suggère deux stratégies.
La première consiste à vérifier un certain nombre de lignes dupliquées dans la clé de recherche:
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)
}
Cela vous obligera à dédoubler le jeu de données avant de l'utiliser:
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 deuxième option consiste à reproduire le comportement d'Excel en prenant la première valeur correspondante de l'ensemble de données de recherche:
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")
Ces fonctions diffèrent légèrement de lookup
car elles ajoutent plusieurs colonnes.