Jak pominąć i wypełnić (jak w Excelu) w R?

84

Mam zbiór danych około 105000 wierszy i 30 kolumn. Mam zmienną kategorialną, którą chciałbym przypisać do liczby. W Excelu prawdopodobnie zrobiłbym coś z VLOOKUPi wypełnił.

Jak bym zrobił to samo w R?

Zasadniczo to, co mam, to HouseTypezmienna i muszę obliczyć HouseTypeNo. Oto kilka przykładowych danych:

HouseType HouseTypeNo
Semi            1
Single          2
Row             3
Single          2
Apartment       4
Apartment       4
Row             3
user2142810
źródło

Odpowiedzi:

118

Jeśli dobrze rozumiem twoje pytanie, oto cztery metody, aby zrobić odpowiednik Excela VLOOKUPi wypełnić za pomocą 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

Oto cztery metody wypełniania pól HouseTypeNoprzy largetableużyciu wartości z lookuptabeli:

Najpierw mergew bazie:

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

Druga metoda z nazwanymi wektorami w bazie:

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

Po trzecie, korzystając z plyrpakietu:

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

Po czwarte, korzystając z sqldfpakietu

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

Jeśli jest możliwe, że niektóre typy domów largetablenie istnieją w lookup, zostanie użyte połączenie lewe:

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

Potrzebne byłyby również odpowiednie zmiany w innych rozwiązaniach.

Czy to właśnie chciałeś zrobić? Daj mi znać, którą metodę lubisz, a dodam komentarz.

Ben
źródło
1
Zdałem sobie sprawę, że to dość późno, ale dzięki za pomoc. Wypróbowałem zarówno pierwszą, jak i drugą metodę. Oba działały dobrze. Jeszcze raz dziękuję za odpowiedź na pytanie!
user2142810
1
Nie ma za co. Jeśli odpowiedział na Twoje pytanie, możesz to zaznaczyć, klikając haczyk pod strzałkami w lewym górnym rogu. Będzie to pomocne dla innych, którzy mają to samo pytanie.
Ben
2
Myślę, że rozwiązanie nr 2 działa tylko dlatego, że w twoim przykładzie unikalne wartości są w kolejności rosnącej (= pierwsza unikalna nazwa to 1, druga unikalna nazwa to 2 i tak dalej). Jeśli dodasz „hous”, powiedzmy w drugim wierszu „HousType = ECII”, HousTypeNo = „17”, wyszukiwanie pójdzie nie tak.
ECII
1
@ECII, proszę, dodaj swoją odpowiedź, która ilustruje problem i pokazuje rozwiązanie
Ben
1
Wspaniały post. Dzięki za udostępnienie! # 4 działał dobrze dla mojej aplikacji ... łącząc dwie bardzo duże tabele 400 MB.
Nathaniel Payne
25

Myślę, że możesz również użyć match():

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

To nadal działa, jeśli pomieszam kolejność lookup.

Ben Bolker
źródło
10

Lubię też używać qdapTools::lookuplub skróconego operatora binarnego %l%. Działa tak samo jak przeglądarka Excela, ale akceptuje argumenty nazw w przeciwieństwie do numerów kolumn

## 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
źródło
6

Rozwiązanie nr 2 odpowiedzi @ Bena nie jest możliwe do odtworzenia w innych, bardziej ogólnych przykładach. Zdarza się, że w tym przykładzie wyszukiwanie jest poprawne, ponieważ unikatowe HouseTypew housespojawiają się w kolejności rosnącej. Spróbuj tego:

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)

Rozwiązanie Bensa nr 2 daje

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

które kiedy

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

kiedy prawidłowa odpowiedź to 17 w tabeli przeglądowej

Właściwy sposób to zrobić

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

Teraz wyszukiwania są wykonywane poprawnie

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

Próbowałem edytować odpowiedź Bensa, ale została odrzucona z powodów, których nie rozumiem.

ECII
źródło
5

Począwszy od:

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

... możesz użyć

as.numeric(factor(houses$HouseType))

... aby nadać unikalny numer każdemu typowi domu. Możesz zobaczyć wynik tutaj:

> 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

... więc otrzymujesz różne liczby w wierszach (ponieważ czynniki są uporządkowane alfabetycznie), ale ten sam wzór.

(EDYCJA: pozostały tekst w tej odpowiedzi jest właściwie zbędny. Przyszło mi do głowy, aby sprawdzić i okazało się, że read.table()domy $ HouseType stały się już czynnikiem, gdy był wczytywany do ramki danych w pierwszej kolejności).

Jednak może być lepiej po prostu przekonwertować HouseType na współczynnik, który dałby wszystkie te same korzyści co HouseTypeNo, ale byłby łatwiejszy do zinterpretowania, ponieważ typy domów są nazwane, a nie numerowane, np .:

> 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"  
Szymon
źródło
5

Plakat nie zawierał pytania o szukanie wartości, jeśli exact=FALSE, ale dodam to jako odpowiedź dla siebie i być może dla innych.

Jeśli szukasz wartości kategorycznych, użyj innych odpowiedzi.

W programie Excel można vlookuprównież dopasować w przybliżeniu wartości liczbowe do czwartego argumentu (1) match=TRUE. Myślę o match=TRUEsprawdzaniu wartości na termometrze. Wartością domyślną jest FALSE, co jest idealne dla wartości jakościowych.

Jeśli chcesz dopasować w przybliżeniu (przeprowadzić wyszukiwanie), R ma funkcję o nazwie findInterval, która (jak sama nazwa wskazuje) znajdzie przedział / przedział, który zawiera ciągłą wartość liczbową.

Jednak powiedzmy, że chcesz findIntervaldla kilku wartości. Możesz napisać pętlę lub użyć funkcji stosującej. Jednak wydaje mi się, że bardziej wydajne jest podejście wektoryzowane DIY.

Powiedzmy, że masz siatkę wartości indeksowanych przez x i 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)))

i masz pewne wartości, które chcesz sprawdzić według x i 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")

Oto zwizualizowany przykład:

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

Wykres konturowy

Możesz znaleźć odstępy x i odstępy y za pomocą tego typu formuły:

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

Możesz pójść o krok dalej i wykonać (uproszczoną) interpolację wartości z w gridnastępujący sposób:

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)

Który daje te wartości:

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)

Wykres konturowy z wartościami

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

Zwróć uwagę, że ix i iy można było również znaleźć za pomocą pętli przy użyciu findInterval, np. Tutaj jest jeden przykład dla drugiego wiersza

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

Które mecze ixi iywdf[2]

Przypis: (1) Czwarty argument funkcji vlookup był wcześniej nazywany „dopasowaniem”, ale po wprowadzeniu wstążki została zmieniona na „[wyszukiwanie_zakresu]”.

geneorama
źródło
4

Możesz użyć mapvalues()z pakietu plyr.

Wstępne dane:

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

Tabela wyszukiwania / przejścia dla pieszych:

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

Utwórz nową zmienną:

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

Lub w przypadku prostych zamian możesz pominąć tworzenie długiej tabeli przeglądowej i zrobić to bezpośrednio w jednym kroku:

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

Wynik:

> 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
źródło
3

Używanie mergeróżni się od wyszukiwania w programie Excel, ponieważ może zduplikować (pomnożyć) dane, jeśli ograniczenie klucza podstawowego nie jest wymuszone w tabeli odnośników lub zmniejszyć liczbę rekordów, jeśli nie używasz all.x = T.

Aby upewnić się, że nie będziesz mieć z tym kłopotów i bezpiecznie wyszukiwać, sugeruję dwie strategie.

Pierwszą z nich jest sprawdzenie liczby zduplikowanych wierszy w kluczu wyszukiwania:

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

Zmusi to Cię do usunięcia duplikatu zestawu danych wyszukiwania przed jego użyciem:

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

Drugą opcją jest odtworzenie zachowania programu Excel poprzez pobranie pierwszej pasującej wartości z zestawu danych wyszukiwania:

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

Te funkcje różnią się nieco od lookupfunkcji dodawania wielu kolumn.

Bulat
źródło