【问题标题】:Sample to have an equal number of each sex within groups in R样本在R中的组内具有相同数量的每种性别
【发布时间】:2012-12-07 17:08:05
【问题描述】:

第一件事,第一。这是我的数据:

lat <- c(12, 12, 58, 58, 58, 58, 58, 45, 45, 45, 45, 45, 45, 64, 64, 64, 64, 64, 64, 64)
long <- c(-14, -14, 139, 139, 139, 139, 139, -68, -68, -68, -68, -68, 1, 1, 1, 1, 1, 1, 1, 1)
sex <- c("M", "M", "M", "M", "F", "M", "M", "F", "M", "M", "M", "F", "M", "F", "M", "F", "F", "F", "F", "M")
score <- c(2, 6, 3, 6, 5, 4, 3, 2, 3, 9, 9, 8, 6, 5, 6, 7, 5, 7, 5, 1)

data <- data.frame(lat, long, sex, score)

数据应如下所示:

   lat long sex score
1   12  -14   M     2
2   12  -14   M     6
3   58  139   M     3
4   58  139   M     6
5   58  139   F     5
6   58  139   M     4
7   58  139   M     3
8   45  -68   F     2
9   45  -68   M     3
10  45  -68   M     9
11  45  -68   M     9
12  45  -68   F     8
13  45    1   M     6
14  64    1   F     5
15  64    1   M     6
16  64    1   F     7
17  64    1   F     5
18  64    1   F     7
19  64    1   F     5
20  64    1   M     1

我想尽办法解决这个问题。变量是纬度、经度、性别和分数。我希望每个位置的男性和女性数量相等(即具有相同的经度和纬度)。例如,第二个位置(第 3 到 7 行)只有一名女性。这个女性应该被保留,其余个体中的一名男性也应该被保留(也许通过随机抽样)。有些地方只有一种性别的信息,例如第一个位置(第 1 行和第 2 行)只有男性数据。应该删除该位置的行(因为没有女性)。一切按计划进行,最终数据集应如下所示:

   lat2 long2 sex2 score2
1    58   139    F      5
2    58   139    M      4
3    45   -68    F      2
4    45   -68    M      3
5    45   -68    M      9
6    45   -68    F      8
7    64     1    M      6
8    64     1    F      5
9    64     1    F      7
10   64     1    M      1

任何帮助将不胜感激。

【问题讨论】:

  • @Mnel 很快就会出现,并解释如何使用data.table

标签: r statistics


【解决方案1】:

这是lapply的解决方案:

data[unlist(lapply(with(data, split(seq.int(nrow(data)), paste(lat, long))),
        # 'split' splits the sequence of row numbers (indices) along the unique
        # combinations of 'lat' and 'long'
        # 'lapply' applies the following function to all sub-sequences
        function(x) {
          # which of the indices are for males:
          male <- which(data[x, "sex"] == "M")
          # which of the indices are for females:
          female <- which(data[x, "sex"] == "F")
          # sample from the indices of males:
          s_male <- sample(male, min(length(male), length(female)))
          # sample from the indices of females:
          s_female <- sample(female, min(length(male), length(female)))
          # combine both sampled indices:
          x[c(s_male, s_female)]                
        })), ]
# The function 'lappy' returns a list of indices which is transformed to a vector
# using 'unlist'. These indices are used to subset the original data frame.

结果:

   lat long sex score
9   45  -68   M     3
11  45  -68   M     9
12  45  -68   F     8
8   45  -68   F     2
7   58  139   M     3
5   58  139   F     5
20  64    1   M     1
15  64    1   M     6
19  64    1   F     5
16  64    1   F     7

【讨论】:

  • 1+ 不错。起初我认为您正在构建大量数据框组件,但后来我意识到您正在使用索引来做这件事。
  • 斯文,感谢您的解决方案。如果您包含有关代码正在做什么的一些信息,这对我真的很有帮助。
  • 比我放在一起的长 if 链优雅得多。
【解决方案2】:

下面是一个快速的方法,它涉及创建一个经纬度组合的临时列。我们根据这一列拆分DF,计算每个拆分中的M/F,适当采样,然后重新组合。

# First, We call the dataframe something other than "data" ;) 
mydf <- data.frame(lat, long, sex, score)

# create a new data frame with a temporary column, which concatenates the lat & long. 
mydf.new <- data.frame(mydf, latlong=paste(mydf$lat, mydf$long, sep=","))

# Split the data frame according to the lat-long location
mydf.splat <- split(mydf.new, mydf.new$latlong)

# eg, taking a look at one of our tables:
mydf.splat[[4]]

sampled <- 
  lapply(mydf.splat, function(tabl) {
    Ms <- sum(tabl$sex=="M")
    Fs <- sum(tabl$sex=="F")

    if(Fs == 0 || Ms ==0)    # If either is zero, we drop that location
      return(NULL)

    if(Fs == Ms)   # If they are both equal, no need to sample. 
      return(tabl)

    # If number of Females less than Males, return all Females 
    #    and sample from males in ammount equal to Females
    if (Fs < Ms)   
      return(tabl[c(which(tabl$sex=="F"), sample(which(tabl$sex=="M"), Fs)),   ])

    if (Ms < Fs)  # same as previous, but for Males < Femals
      return(tabl[c(which(tabl$sex=="M"), sample(which(tabl$sex=="F"), Ms)),   ])

    stop("hmmm... something went wrong.")  ## We should never hit this line, but just in case. 
  })

# Flatten into a single table
mydf.new <- do.call(rbind, sampled)

# Clean up
row.names(mydf.new) <- NULL  # remove the row names that were added
mydf.new$latlong <- NULL     # remove the temporary column that we added

结果

mydf.new

#    lat long sex score
# 1   45  -68   F     2
# 2   45  -68   F     8
# 3   45  -68   M     9
# 4   45  -68   M     3
# 5   58  139   F     5
# 6   58  139   M     3
# 7   64    1   M     6
# 8   64    1   M     1
# 9   64    1   F     7
# 10  64    1   F     5

【讨论】:

  • 您的第一步可以使用 split( ..., drop=TRUE) 更经济地完成,并且在 rbind 步骤期间返回没有行的数据框将“消失”。
  • 感谢这个里卡多的一百万!虽然它不是提供的最经济的解决方案,但它绝对是对我最有帮助的。我仍然在掌握 R 并且 cmets 在理解代码实际在做什么方面非常有用。
  • @DWin,不确定我是否关注...?用于创建sampled 的拆分列表不包含任何空行。结果sampled 确实,并且它们确实在do.call 步骤中消失了。你能澄清我忽略了什么吗?谢谢
  • @Ciaran,很高兴它有帮助。我在最近的编辑中清理了一些 cmets。
  • @RicardoSaporta:我只是认为不需要创建 latlong 字符向量。你可以只做split(df, fac1, fac2, drop=TRUE) 并且名称是自动创建的。
【解决方案3】:

这会将值作为列表元素返回:

spl <- split(data, interaction(data$lat, data$long) ,drop=TRUE)
           # interaction creates all the two way pairs from those two vectors
           # drop is needed to eliminate the dataframes with no representation
res <- lapply(spl, function(x) { #First find the nuber of each gender to select
                        N=min(table(x$sex))  # then sample each sex separately
   rbind( x[ x$sex=="M" & row.names(x) %in% sample(row.names(x[x$sex=="M",] ), N) , ],
                 # One (or both) of these will be "sampling" all of that sex.
          x[ x$sex=="F" & row.names(x) %in% sample(row.names(x[x$sex=="F", ]), N) , ] )
                                } )
res
#------------
$`45.-68`  
   lat long sex score
9   45  -68   M     3
11  45  -68   M     9
8   45  -68   F     2
12  45  -68   F     8

$`12.-14` # So there were no women in this group and zero could be matched
[1] lat   long  sex   score
<0 rows> (or 0-length row.names)

$`45.1`
[1] lat   long  sex   score
<0 rows> (or 0-length row.names)

$`64.1`
   lat long sex score
15  64    1   M     6
20  64    1   M     1
16  64    1   F     7
17  64    1   F     5

$`58.139`
  lat long sex score
7  58  139   M     3
5  58  139   F     5

,,,, 但是如果你想要它作为一个数据框你可以使用do.call(rbind, res):

> do.call(rbind, res)
          lat long sex score
45.-68.10  45  -68   M     9
45.-68.11  45  -68   M     9
45.-68.8   45  -68   F     2
45.-68.12  45  -68   F     8
64.1.15    64    1   M     6
64.1.20    64    1   M     1
64.1.17    64    1   F     5
64.1.18    64    1   F     7
58.139.6   58  139   M     4
58.139.5   58  139   F     5

【讨论】:

  • 感谢这个 DWin。正如我在上面提到的关于 Sven 的解决方案的那样,如果你能解释代码在做什么,这对我也会有帮助。我只使用 R 几个月,但我仍在掌握它。干杯!
  • 我不知道interaction!很有用。我明白你现在所说的关于使用drop 的内容。我没有交互,而是创建了一个虚拟列
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-13
  • 1970-01-01
  • 2015-03-21
  • 2023-03-06
  • 2018-07-10
  • 1970-01-01
  • 2020-04-24
相关资源
最近更新 更多