Як зробити vlookup та заповнити (як у Excel) у R?


84

У мене є набір даних близько 105000 рядків і 30 стовпців. У мене є категоріальна змінна, яку я хотів би віднести до числа. В Excel я, мабуть, щось зробив би VLOOKUPі заповнив.

Як би я зробив те саме R?

По суті, те, що я маю, є HouseTypeзмінною, і мені потрібно обчислити HouseTypeNo. Ось кілька зразків даних:

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

Відповіді:


118

Якщо я правильно зрозумів ваше запитання, ось чотири методи зробити еквівалент Excel VLOOKUPі заповнити за допомогою 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

Ось чотири методи заповнення HouseTypeNoза largetableдопомогою значень у lookupтаблиці:

Спочатку з mergeбазою:

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

Другий метод з названими векторами в основі:

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

По-третє, використовуючи plyrпакет:

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

По-четверте, використання 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")

Якщо можливо, що деякі типи будинків в largetableне існують, lookupтоді буде використано ліве з'єднання:

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

Потрібні були б також відповідні зміни до інших рішень.

Це те, що ти хотів зробити? Повідомте мене, який метод вам подобається, і я додаю коментар.


1
Я зрозумів, що це досить пізно, але дякую за вашу допомогу. Я спробував і перший, і другий метод. Обидва вони добре працювали. Ще раз дякую за відповідь на питання!
user2142810 04

1
Ласкаво просимо. Якщо він відповів на ваше запитання, ви можете вказати це, натиснувши галочку під стрілками у верхньому лівому куті. Це буде корисно іншим, хто має таке саме запитання.
Бен,

2
Я думаю, що рішення №2 працює лише тому, що у вашому прикладі унікальні значення трапляються у зростаючому порядку (= перше унікальне ім’я 1, друге унікальне ім’я 2 і так далі). Якщо додати в 'hous', скажімо, у другому рядку сказати 'HousType = ECII', HousTypeNo = '17 ', пошук все піде неправильно.
ECII

1
@ECII, будь ласка, додайте свою відповідь, яка ілюструє проблему та показує ваше рішення
Бен,

1
Чудовий пост. Дякую, що поділились! №4 добре працював для мого додатка ... об’єднавшись у дві дуже великі таблиці розміром 400 МБ.
Натаніель Пейн,

25

Я думаю, ви також можете використовувати match():

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

Це все ще працює, якщо я скремблюю порядок lookup.


10

Мені також подобається використовувати qdapTools::lookupабо скорочений двійковий оператор %l%. Він працює ідентично вигляду Excel, але він приймає аргументи імен, протилежні номерам стовпців

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

6

Рішення №2 відповіді @ Ben не відтворюється в інших більш загальних прикладах. Трапляється, щоб дати правильний пошук у прикладі, тому що унікальний HouseTypein housesвідображається у зростаючому порядку. Спробуйте це:

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 №2 дає

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

який коли

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

коли правильна відповідь - 17 із таблиці пошуку

Правильний спосіб це зробити

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

Тепер пошук виконується правильно

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

Я намагався відредагувати відповідь Bens, але її відхиляють із незрозумілих мені причин.


5

Починаючи з:

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

... ви можете використовувати

as.numeric(factor(houses$HouseType))

... дати унікальний номер для кожного типу будинку. Результат ви можете побачити тут:

> 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

... отже, ви отримуєте різні числа в рядках (оскільки фактори впорядковані за алфавітом), але однаковий шаблон.

(РЕДАКТУВАТИ: текст, що залишився у цій відповіді, насправді зайвий. Мені спало на думку перевірити, і виявилося, що read.table()вже зробив будинки $ HouseType фактором, коли його спочатку читали у фрейм даних).

Однак вам може бути краще просто перетворити HouseType на коефіцієнт, який дасть вам ті самі переваги, що і HouseTypeNo, але простіше буде інтерпретувати, оскільки типи будинків називаються, а не нумеруються, наприклад:

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

5

Плакат не запитував про пошук значень if exact=FALSE, але я додаю це як відповідь для власного посилання та, можливо, інших.

Якщо ви шукаєте категоричні значення, використовуйте інші відповіді.

Excel vlookupтакож дозволяє збігатись приблизно для числових значень з 4-м аргументом (1) match=TRUE. Я думаю, match=TRUEяк шукати значення на термометрі. Значення за замовчуванням - FALSE, що ідеально підходить для категоріальних значень.

Якщо ви хочете приблизно відповідати (виконати пошук), R має функцію findInterval, яка називається , яка (як випливає з назви) знайде інтервал / кошик, що містить ваше безперервне числове значення.

Однак, скажімо, ви хочете findIntervalдля кількох значень. Ви можете написати цикл або скористатися функцією застосування. Однак я вважаю, що ефективніше застосовувати векторизований підхід "зроби сам".

Скажімо, у вас є сітка значень, проіндексованих x та 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)))

і у вас є деякі значення, які ви хочете знайти за x та 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")

Ось візуалізований приклад:

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

Контурна ділянка

Ви можете знайти інтервали x та 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

Ви можете зробити на крок далі і виконати (спрощену) інтерполяцію значень z приблизно gridтак:

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)

Що дає вам ці значення:

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

Зверніть увагу, що ix та iy також могли бути знайдені за допомогою циклу findInterval, наприклад, ось один приклад для другого рядка

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

Який матчі ixта iyвdf[2]

Виноска: (1) Четвертий аргумент vlookup раніше називався "match", але після того, як вони ввели стрічку, її перейменовано на "[range_lookup]".


4

Ви можете використовувати mapvalues()з пакету plyr.

Початкові дані:

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

Створіть нову змінну:

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

Або для простих замін ви можете пропустити створення довгої таблиці пошуку і зробити це безпосередньо за один крок:

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

Результат:

> 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

3

Використання mergeвідрізняється від пошуку в Excel, оскільки воно може дублювати (примножувати) ваші дані, якщо обмеження первинного ключа не застосовується в таблиці пошуку або зменшує кількість записів, якщо ви не використовуєте all.x = T.

Щоб переконатись, що ви не зіткнулися з цим і безпечно шукали, я пропоную дві стратегії.

Перший - перевірити кількість повторених рядків у ключі пошуку:

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

Це змусить вас зняти дублювання набору даних перед використанням:

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

Другий варіант - відтворити поведінку Excel, взявши перше відповідне значення з набору даних підстановки:

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

Ці функції трохи відрізняються від функцій, lookupоскільки вони додають кілька стовпців.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.