2013-03-08 3 views
58

У меня есть набор данных около 105000 строк и 30 столбцов. У меня есть категориальная переменная, которую я хотел бы присвоить ей. В Excel я бы, вероятно, что-то сделал с VLOOKUP и заполнил.Как сделать vlookup и заполнить (как в Excel) в R?

Как мне сделать то же самое в R?

По существу, у меня есть переменная HouseType, и мне нужно рассчитать HouseTypeNo. Вот некоторые примерные данные:

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

ответ

89

Если я правильно понимаю ваш вопрос, здесь четыре способа сделать эквивалент в 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

+0

Добро пожаловать. Если он ответил на ваш вопрос, вы можете указать это, нажав на галочку под стрелками в верхнем левом углу. Это будет полезно для других, у кого есть тот же вопрос. – Ben

+2

Я думаю, что решение №2 работает только потому, что в вашем примере уникальные значения оказываются в порядке возрастания (= первое уникальное имя - 1 второе уникальное имя - 2 и т. Д.). Если вы добавите в 'hous', скажем, в * второй строке * 'HousType = ECII', HousTypeNo = '17 ', поиск идет не так. – ECII

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 

... так что вы в конечном итоге с различными номерами по строкам (так как коэффициенты упорядочены в алфавитном порядке), но по той же схеме.

(EDIT: оставшийся текст в этом ответе на самом деле является избыточным. Мне пришло в голову проверить, и оказалось, что read.table() уже сделал дома $ HouseType фактором, когда он был впервые прочитан в dataframe) ,

Однако вполне может быть лучше просто преобразовать 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" 
9

Мне также нравится использовать 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 из @ ответ Бен не воспроизводим в других более общих примерах. Это приводит к правильному поиску в примере, потому что уникальные HouseType в 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 ответ, но это отвергается по причинам, я не могу понять.

11

Я думаю, вы также можете использовать match():

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

Это все еще работает, если я засекретить порядок lookup.

3

Вы можете использовать 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 
4

Плакат не просил о поиске значения, если exact=FALSE, но я добавляю это как ответ для своих собственных ссылок и, возможно, других.

Если вы ищете категориальные значения, используйте другие ответы.

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

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

Однако предположим, что вы хотите получить findInterval для нескольких значений. Вы можете написать цикл или использовать функцию apply. Тем не менее, я счел более эффективным использовать подход, основанный на DIY.

Давайте предположим, что у вас есть сетка значений, проиндексированных по х и у:

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

и у вас есть некоторые значения, которые вы хотите посмотреть на х и у:

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) 

Contour Plot

Вы можете найти интервалы х и у интервалов с этим типом формулы:

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 

Вы могли бы сделать еще один шаг вперед и выполнить (упрощенно) интерполяции по значениям г в 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) 

Contour plot with values

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) Четвертый аргумент ВПР ранее назывался «Матч», но после того, как они представили ленту он было переименовано в «[range_lookup]».

2

Использование 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, поскольку они добавляют несколько столбцов.

 Смежные вопросы

  • Нет связанных вопросов^_^