使用来自R

问题描述

这是我第一次在这里发布问题,所以如果我不遵循某些准则,请告诉我,我将立即对其进行更改。

基本上,我的问题如下:我有两个数据集(为简单起见,分别称为数据集A和数据集B)由一系列共同的列组成,这些列包括每个人/观察/的社会人口统计特征行。我需要的是数据集A中的每个观测/行,我必须从数据集B中选择一个随机观测,该观测具有与关键的社会人口统计变量有关的匹配特征。为了说明,我准备了一个简单的示例:

library("dplyr")

A = data.frame(nuts2 = c(1,1,2,3,3),gender = c(1,2))
B = data.frame(nuts2 = c(rep(1,10),rep(2,rep(3,10)),gender=c(rep(1,5),rep(1,5)))

A <- A[sample(1:nrow(A)),] %>% mutate(id = seq(1:nrow(A)))
B <- B[sample(1:nrow(B)),] %>% mutate(id = seq(1:nrow(B)))

我正在尝试避免forloops,因为在R中这似乎被认为是不好的做法,因此我尝试创建一个函数并使用apply在每个观察值上运行它。假设我们希望匹配来自B的随机观察ID,该ID与A中的观察具有相同的性别和nuts2值,我的代码如下:

matching_fun <- function(x) {
  donor <- B %>% filter(gender == A$gender & nuts2 == A$nuts2) %>% sample_n(1) 
  donor_id <- donor$id
  return(donor_id)
}

A$donor_id <- apply(A,matching_fun)

我希望这会导致一个数据帧包含A中存在的所有信息和一个名为don_id的额外列,并通过B中的社会人口统计学小组的随机抽样识别出相应的供体ID。

但是,我的代码无法正确执行比赛,也没有遵守社会人口统计特征。谁能告诉我我在做什么错?

在此先感谢您的支持/评论/批评。

注意:我的数据集每个都有近200万个观测值,我将不得不在多个测试中使用它。因此,计算效率具有一定的重要性。

解决方法

这是一种data.table方法

setDT(A); setDT(B)
A[B,on = .(nuts2,gender)][,.(id = i.id[[sample(.N,1L)]]),by = .(nuts2,gender)]

输出

> set.seed(1L)
> A[B,gender)]
   nuts2 gender id
1:     1      2  1
2:     2      2 16
3:     2      1  4
4:     1      1  6
5:     3      2 25
6:     3      1 22
> A[B,gender)]
   nuts2 gender id
1:     1      2  8
2:     2      2 10
3:     2      1 24
4:     1      1  5
5:     3      2 25
6:     3      1 29
> A[B,gender)]
   nuts2 gender id
1:     1      2  8
2:     2      2  3
3:     2      1  4
4:     1      1 18
5:     3      2 25
6:     3      1 13

更新

我重新格式化了代码,以帮助您了解其背后的逻辑。

library(data.table)
setDT(A); setDT(B)
A[
  B,gender)
][,.(id = id[[1L]],donor_id = i.id[[sample(.N,gender)
]

输出看起来像这样

   nuts2 gender id donor_id
1:     2      1  1       15
2:     1      2  2        2
3:     3      2  5        8
4:     2      2  3        6
5:     1      1  4        9
6:     3      1  6       22

上面的代码本质上与下面的dplyr管道相同,只是效率更高。

left_join(A,B,by = c("nuts2","gender")) %>% rename(id = id.x,donor_id = id.y) %>% group_by(nuts2,gender) %>% slice_sample(n = 1L)

如果仍然难以理解,请参见下图:

Let A and B be the dataframes as follows:
       A                   B
nuts2 gender id     nuts2 gender  id
    1      1  1         1      1   2
    2      1  3         1      1   7
                        2      1  13
                        2      1   4
                        2      1   6
What you want to do is:

First,match these groups
       A                   B
nuts2 gender id     nuts2 gender  id
    1      1  1         1      1   2
                        1      1   7
---------------     ----------------
    2      1  3         2      1  13
                        2      1   4
                        2      1   6

Second,slice a sample for each group in B
       A                   B
nuts2 gender id     nuts2 gender  id
    1      1  1         1      1   2
                        1      1   7 <--- RNG gives you this
---------------     ----------------
    2      1  3         2      1  13
                        2      1   4 <--- RNG gives you this
                        2      1   6 

Then,create a new variable in A with the id in B and call it donor_id
       A                            B
nuts2 gender id donor_id     nuts2 gender  id
    1      1  1        7         1      1   2
                                 1      1   7 <--- RNG gives you this
------------------------     ----------------
    2      1  3        4         2      1  13
                                 2      1   4 <--- RNG gives you this
                                 2      1   6 

However,an equivalent but more efficient way is 

First,join A and B on/by nuts2 and gender. In this way we can determine the population that we want to sample from.
       A                     B
+---------------+       +----------------+                       +----------------------+
|nuts2 gender id|       |nuts2 gender  id|                       |nuts2 gender id.A id.B|
|    1      1  1|       |    1      1   2|   by (nuts2,gender)  |    1      1    1    2|
|    2      1  3|   +   |    1      1   7|        =======>       |    1      1    1    7|
+---------------+       |    2      1  13|                       |    2      1    3   13|
                        |    2      1   4|                       |    2      1    3    4|
                        |    2      1   6|                       |    2      1    3    6|
                        +----------------+                       +----------------------+

Then,just slice a sample row within each (nuts2,gender) group and rename id.A and id.B as id and donor_id,respectively.
        A + B
               id donor_id
nuts2 gender XXXX     XXXX
    1      1    1        2
    1      1    1        7 <--- RNG gives you this
--------------------------
    2      1    3       13
    2      1    3        4 <--- RNG gives you this
    2      1    3        6

这就是我的代码。

这部分意味着在nuts2gender的每个匹配组上加入A和B。

A[
  B,gender)
]

然后,我们在.Nnuts2每组中的gender行中切出一个示例行。 .Ndata.table包中的保留字;它为您提供每个组中的行数。之所以有i.id,是因为A和B都具有列id,并且data.table在加入之后自动将B的id重命名为i.id。另外,我们只需要id[[1L]],因为每个组中的id是相同的。

A[
  B,gender)
]
,

如果您想避免forloops,也许if / else循环会适合您:

library("dplyr")

#set.seed(1)

A = data.frame(nuts2 = c(1,1,2,3,3),gender = c(1,2))
B = data.frame(nuts2 = c(rep(1,10),rep(2,rep(3,10)),gender=c(rep(1,5),rep(1,5)))

A <- A[sample(1:nrow(A)),] %>% mutate(id = seq(1:nrow(A)))
B <- B[sample(1:nrow(B)),] %>% mutate(id = seq(1:nrow(B)))


  if (is.element(A$gender,B$gender) & is.element(A$nuts2,B$nuts2)){
  donor_id <- sample(B$id,6)
  filter<-A[is.element(A$gender,B$nuts2),]
  result<-cbind(filter,donor_id)[-3]
  print(result)
  }else{
  print("No matching characteristics")
}