Wie mache ich vlookup und fülle es aus (wie in Excel) in R?

82

Ich habe einen Datensatz über 105000 Zeilen und 30 Spalten. Ich habe eine kategoriale Variable, die ich einer Nummer zuweisen möchte. In Excel würde ich wahrscheinlich etwas damit machen VLOOKUPund füllen.

Wie würde ich das Gleiche tun R?

Im Wesentlichen habe ich eine HouseTypeVariable, und ich muss die berechnen HouseTypeNo. Hier einige Beispieldaten:

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

Antworten:

116

Wenn ich Ihre Frage richtig verstehe, gibt es vier Methoden, um das Äquivalent zu Excel zu erreichen VLOOKUPund Folgendes auszufüllen 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

Hier sind vier Methoden, um HouseTypeNodas largetablemit den Werten in der lookupTabelle auszufüllen :

Zuerst mit mergein Basis:

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

Eine zweite Methode mit benannten Vektoren in der Basis:

# 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]))

Drittens mit dem plyrPaket:

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

Viertens mit dem sqldfPaket

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

Wenn es möglich ist, dass einige Haustypen in largetablenicht vorhanden sind, lookupwird ein linker Join verwendet:

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

Entsprechende Änderungen an den anderen Lösungen wären ebenfalls erforderlich.

Wolltest du das tun? Lassen Sie mich wissen, welche Methode Ihnen gefällt, und ich werde einen Kommentar hinzufügen.

Ben
quelle
1
Mir wurde klar, dass dies ziemlich spät ist, aber danke für Ihre Hilfe. Ich habe sowohl die erste als auch die zweite Methode ausprobiert. Beide haben gut funktioniert. Nochmals vielen Dank für die Beantwortung der Frage!
user2142810
1
Bitte. Wenn es Ihre Frage beantwortet hat, können Sie dies anzeigen, indem Sie auf das Häkchen unter den Pfeilen oben links klicken. Das ist hilfreich für andere, die die gleiche Frage haben.
Ben
2
Ich denke, Lösung Nr. 2 funktioniert nur, weil in Ihrem Beispiel die eindeutigen Werte in aufsteigender Reihenfolge sind (= der erste eindeutige Name ist 1, der zweite eindeutige Name ist 2 usw.). Wenn Sie in 'Hous' hinzufügen, sagen wir in der zweiten Zeile 'HousType = ECII', HousTypeNo = '17 ', geht die Suche schief.
ECII
1
@ ECII Bitte fahren Sie fort und fügen Sie Ihre Antwort hinzu, die das Problem veranschaulicht und Ihre Lösung zeigt
Ben
1
Guter Eintrag. Danke für das Teilen! # 4 hat für meine Anwendung gut funktioniert ... zwei sehr große 400-MB-Tabellen.
Nathaniel Payne
25

Ich denke, Sie können auch verwenden match():

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

Dies funktioniert immer noch, wenn ich die Reihenfolge von verschlüssele lookup.

Ben Bolker
quelle
10

Ich benutze auch gerne einen qdapTools::lookupbinären Operator oder eine Kurzform %l%. Es funktioniert identisch mit einem Excel-Lookup, akzeptiert jedoch Namensargumente, die Spaltennummern entgegengesetzt sind

## 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
maloneypatr
quelle
6

Lösung 2 von @ Bens Antwort ist in anderen allgemeineren Beispielen nicht reproduzierbar. Es passiert die richtige Lookup im Beispiel zu geben , weil die einzigartigen HouseTypein housesaufsteigender Reihenfolge erscheinen. Versuche dies:

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 Lösung # 2 gibt

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

Welches wann

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

wenn die richtige Antwort 17 aus der Nachschlagetabelle ist

Der richtige Weg ist 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]))

Jetzt werden die Suchvorgänge korrekt durchgeführt

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

Ich habe versucht, die Antwort von Bens zu bearbeiten, aber sie wird aus Gründen abgelehnt, die ich nicht verstehen kann.

ECII
quelle
5

Beginnen mit:

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

... können Sie verwenden

as.numeric(factor(houses$HouseType))

... um für jeden Haustyp eine eindeutige Nummer zu vergeben. Sie können das Ergebnis hier sehen:

> 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

... so dass Sie am Ende unterschiedliche Zahlen in den Zeilen haben (weil die Faktoren alphabetisch geordnet sind), aber dasselbe Muster.

(BEARBEITEN: Der verbleibende Text in dieser Antwort ist tatsächlich überflüssig. Mir fiel ein, dies zu überprüfen, und es stellte sich heraus, dass read.table()Häuser $ HouseType bereits zu einem Faktor geworden waren, als es überhaupt in den Datenrahmen eingelesen wurde.)

Es ist jedoch möglicherweise besser, HouseType in einen Faktor umzuwandeln, der Ihnen dieselben Vorteile wie HouseTypeNo bietet, aber einfacher zu interpretieren ist, da die Haustypen eher benannt als nummeriert sind, z.

> 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"  
Simon
quelle
5

Das Poster hat nicht nach dem exact=FALSENachschlagen von Werten gefragt , aber ich füge dies als Antwort für meine eigene Referenz und möglicherweise für andere hinzu.

Wenn Sie nach kategorialen Werten suchen, verwenden Sie die anderen Antworten.

Mit Excel vlookupkönnen Sie auch die Übereinstimmung für numerische Werte ungefähr mit dem 4. Argument (1) abgleichen match=TRUE. Ich denke daran match=TRUE, Werte auf einem Thermometer nachzuschlagen. Der Standardwert ist FALSE, was perfekt für kategoriale Werte ist.

Wenn Sie ungefähr übereinstimmen möchten (eine Suche durchführen), hat R eine Funktion namens findInterval, die (wie der Name schon sagt) das Intervall / den Bin findet, das Ihren fortlaufenden numerischen Wert enthält.

Angenommen, Sie möchten findIntervalfür mehrere Werte. Sie können eine Schleife schreiben oder eine Apply-Funktion verwenden. Ich habe es jedoch effizienter gefunden, einen vektorisierten DIY-Ansatz zu wählen.

Angenommen, Sie haben ein durch x und y indiziertes Wertegitter:

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)))

und Sie haben einige Werte, die Sie mit x und y nachschlagen möchten:

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")

Hier ist das visualisierte Beispiel:

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

Konturdiagramm

Sie können die x-Intervalle und y-Intervalle mit dieser Art von Formel finden:

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

Sie können noch einen Schritt weiter gehen und eine (vereinfachte) Interpolation der z-Werte gridwie folgt durchführen:

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)

Welches gibt Ihnen diese Werte:

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)

Konturdiagramm mit Werten

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

Beachten Sie, dass ix und iy auch mit einer Schleife gefunden werden könnten findInterval, z. B. hier ein Beispiel für die zweite Zeile

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

Welche Spiele ixund iyindf[2]

Fußnote: (1) Das vierte Argument von vlookup hieß zuvor "match", wurde jedoch nach Einführung des Menübands in "[range_lookup]" umbenannt.

Genorama
quelle
4

Sie können mapvalues()aus dem Plyr-Paket verwenden.

Anfangsdaten:

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

Lookup / Zebrastreifen Tisch:

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

Erstellen Sie die neue Variable:

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

Oder für einfache Ersetzungen können Sie das Erstellen einer langen Nachschlagetabelle überspringen und dies direkt in einem Schritt tun:

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

Ergebnis:

> 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
Sam Firke
quelle
3

Die Verwendung mergeunterscheidet sich von der Suche in Excel, da sie möglicherweise Ihre Daten dupliziert (multipliziert), wenn die Primärschlüsseleinschränkung in der Nachschlagetabelle nicht erzwungen wird, oder die Anzahl der Datensätze verringert, wenn Sie sie nicht verwenden all.x = T.

Um sicherzustellen, dass Sie damit nicht in Schwierigkeiten geraten und sicher nachschlagen, schlage ich zwei Strategien vor.

Zunächst müssen Sie eine Reihe von doppelten Zeilen im Suchschlüssel überprüfen:

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)
}

Dadurch werden Sie gezwungen, das Lookup-Dataset zu deaktivieren, bevor Sie es verwenden:

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
# ...

Die zweite Option besteht darin, das Excel-Verhalten zu reproduzieren, indem der erste übereinstimmende Wert aus dem Suchdatensatz übernommen wird:

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")

Diese Funktionen unterscheiden sich geringfügig von denen, lookupda sie mehrere Spalten hinzufügen.

Bulat
quelle