Làm thế nào để thực hiện vlookup và điền xuống (như trong Excel) trong R?


82

Tôi có một tập dữ liệu khoảng 105000 hàng và 30 cột. Tôi có một biến phân loại mà tôi muốn gán nó cho một số. Trong Excel, tôi có thể sẽ làm gì đó với VLOOKUPvà điền.

Làm thế nào tôi sẽ làm điều tương tự trong R?

Về cơ bản, những gì tôi có là một HouseTypebiến và tôi cần phải tính toán HouseTypeNo. Dưới đây là một số dữ liệu mẫu:

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

Câu trả lời:


116

Nếu tôi hiểu câu hỏi của bạn một cách chính xác, đây là bốn phương pháp để làm tương tự với Excel VLOOKUPvà điền vào bằng cách sử dụng 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

Dưới đây là bốn phương pháp để lấp đầy HouseTypeNotrong largetablesử dụng các giá trị trong lookupbảng:

Đầu tiên với mergecơ sở:

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

Phương thức thứ hai với các vectơ được đặt tên trong cơ sở:

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

Thứ ba, sử dụng plyrgói:

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

Thứ tư, sử dụng sqldfgói

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

Nếu có thể một số kiểu nhà trong largetablekhông tồn tại lookupthì một phép nối bên trái sẽ được sử dụng:

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

Các thay đổi tương ứng đối với các giải pháp khác cũng sẽ cần thiết.

Đó có phải là những gì bạn muốn làm? Hãy cho tôi biết bạn thích phương pháp nào và tôi sẽ thêm phần bình luận.


1
Tôi nhận ra điều này là khá muộn, nhưng cảm ơn sự giúp đỡ của bạn. Tôi đã thử cả phương pháp thứ nhất và thứ hai. Cả hai đều hoạt động tốt. Một lần nữa, cảm ơn bạn đã trả lời câu hỏi!
user2142810

1
Không có gì. Nếu nó trả lời câu hỏi của bạn, bạn có thể chỉ ra điều này bằng cách nhấp vào đánh dấu dưới mũi tên phía trên bên trái. Điều đó sẽ hữu ích cho những người khác có cùng câu hỏi.
Ben

2
Tôi nghĩ giải pháp số 2 chỉ hoạt động vì trong ví dụ của bạn, các giá trị duy nhất xảy ra theo thứ tự tăng dần (= tên duy nhất đầu tiên là 1, tên duy nhất thứ hai là 2, v.v.). Nếu bạn thêm vào 'hous', hãy nói ở hàng thứ hai 'HousType = ECII', HousTypeNo = '17 ', tra cứu hoàn toàn sai.
ECII

1
@ECII vui lòng tiếp tục và thêm câu trả lời của bạn để minh họa vấn đề và hiển thị giải pháp của bạn
Ben

1
Bài rất hay. Cám ơn vì đã chia sẻ! # 4 hoạt động tốt cho ứng dụng của tôi ... kết hợp trên hai bảng rất lớn, 400MB.
Nathaniel Payne

25

Tôi nghĩ bạn cũng có thể sử dụng match():

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

Điều này vẫn hoạt động nếu tôi xáo trộn thứ tự lookup.


10

Tôi cũng thích sử dụng qdapTools::lookuphoặc toán tử nhị phân viết tắt %l%. Nó hoạt động giống như vlookup Excel, nhưng nó chấp nhận các đối số tên đối lập với số cột

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

Giải pháp số 2 trong câu trả lời của @ Ben không thể lặp lại trong các ví dụ khác chung chung hơn. Nó xảy ra để cung cấp cho việc tra cứu đúng trong ví dụ vì độc đáo HouseTypetrong housesxuất hiện trong thứ tự tăng dần. Thử đi:

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 giải pháp # 2 mang lại

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

mà khi nào

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

khi câu trả lời đúng là 17 từ bảng tra cứu

Cách chính xác để làm điều đó là

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

Bây giờ các tra cứu được thực hiện chính xác

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

Tôi đã cố gắng chỉnh sửa câu trả lời của Bens nhưng nó bị từ chối vì lý do tôi không thể hiểu được.


5

Bắt đầu với:

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

... bạn có thể dùng

as.numeric(factor(houses$HouseType))

... để cung cấp một số duy nhất cho từng loại nhà. Bạn có thể xem kết quả tại đây:

> 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

... vì vậy bạn kết thúc với các số khác nhau trên các hàng (vì các yếu tố được sắp xếp theo thứ tự bảng chữ cái) nhưng cùng một mẫu.

(CHỈNH SỬA: văn bản còn lại trong câu trả lời này thực sự là thừa. Tôi chợt kiểm tra và hóa ra là read.table() đã biến nhà $ HouseType thành một yếu tố khi nó được đọc vào khung dữ liệu ngay từ đầu).

Tuy nhiên, tốt hơn hết là bạn nên chuyển đổi HouseType thành một thừa số, điều này sẽ mang lại cho bạn tất cả các lợi ích tương tự như HouseTypeNo, nhưng sẽ dễ hiểu hơn vì các loại nhà được đặt tên thay vì được đánh số, ví dụ:

> 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

Người đăng không hỏi về việc tra cứu các giá trị nếu exact=FALSE, nhưng tôi thêm điều này như một câu trả lời để tham khảo cho riêng tôi và có thể những người khác.

Nếu bạn đang tìm kiếm các giá trị phân loại, hãy sử dụng các câu trả lời khác.

Excel vlookupcũng cho phép bạn so khớp khớp gần đúng cho các giá trị số với đối số thứ 4 (1) match=TRUE. Tôi nghĩ vềmatch=TRUE giống như tra cứu các giá trị trên nhiệt kế. Giá trị mặc định là FALSE, hoàn hảo cho các giá trị phân loại.

Nếu bạn muốn so khớp gần đúng (thực hiện tra cứu), R có một hàm được gọi findInterval, hàm này (như tên của nó) sẽ tìm khoảng / bin chứa giá trị số liên tục của bạn.

Tuy nhiên, giả sử rằng bạn muốn findIntervalmột số giá trị. Bạn có thể viết một vòng lặp hoặc sử dụng một hàm áp dụng. Tuy nhiên, tôi thấy việc áp dụng phương pháp vector hóa DIY sẽ hiệu quả hơn.

Giả sử bạn có một lưới các giá trị được lập chỉ mục bởi x và 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)))

và bạn có một số giá trị bạn muốn tra cứu theo x và 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")

Đây là ví dụ được hình dung:

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

Đường bao lô

Bạn có thể tìm khoảng x và khoảng y bằng loại công thức sau:

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

Bạn có thể tiến thêm một bước nữa và thực hiện phép nội suy (đơn giản) trên các giá trị z gridnhư sau:

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)

Điều này mang lại cho bạn những giá trị sau:

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)

Biểu đồ đường viền với các giá trị

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

Lưu ý rằng ix và iy cũng có thể được tìm thấy với một vòng lặp bằng cách sử dụng findInterval, ví dụ: đây là một ví dụ cho hàng thứ hai

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

Phù hợp ixiytrongdf[2]

Chú thích cuối trang: (1) Đối số thứ tư của vlookup trước đây được gọi là "match", nhưng sau khi họ giới thiệu dải băng, nó được đổi tên thành "[range_lookup]".


4

Bạn có thể sử dụng mapvalues()từ gói plyr.

Dữ liệu ban đầu:

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

Bảng tra cứu / băng qua đường:

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

Tạo biến mới:

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

Hoặc để thay thế đơn giản, bạn có thể bỏ qua việc tạo bảng tra cứu dài và thực hiện việc này trực tiếp trong một bước:

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

Kết quả:

> 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

Việc sử dụng mergekhác với tra cứu trong Excel vì nó có khả năng sao chép (nhân) dữ liệu của bạn nếu ràng buộc khóa chính không được thực thi trong bảng tra cứu hoặc giảm số lượng bản ghi nếu bạn không sử dụng all.x = T.

Để đảm bảo bạn không gặp rắc rối với điều đó và tra cứu một cách an toàn, tôi đề xuất hai chiến lược.

Đầu tiên là kiểm tra một số hàng trùng lặp trong khóa tra cứu:

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

Điều này sẽ buộc bạn phải loại bỏ tập dữ liệu tra cứu trước khi sử dụng nó:

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

Tùy chọn thứ hai là tái tạo hành vi Excel bằng cách lấy giá trị khớp đầu tiên từ tập dữ liệu tra cứu:

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

Các chức năng này hơi khác lookupkhi chúng thêm nhiều cột.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.