使用 tidyverse 清理排名选择调查

Using tidyverse to clean up rank-choice survey

我在 R 中有这样的调查数据,其中我向人们展示了两组动作 - 高和低 - 并要求他们对每个动作进行排名。每组包含独特的动作,用字母标记(总共 6 个动作)。

 id   A_High   B_High   C_High   D_Low   E_Low    F_Low
001       5         2         1      6       4        3
002       6         4         3      5       2        1
003       3         1         6      2       4        5
004       6         5         2      1       3        4

我需要一个如下所示的新 df,其中每个高操作都分配了一个新的数字等级(介于 0 和 3 之间),对应于排名在 下方的低操作项目的数量 高动作。

例如,id 001 的人排名 A_High 排名第 5,B_High 排名第 2,C_High 排名第 1。A_High' s 的新排名将是 1(因为只有 1 个低行动,D_Low 排名低于 A_High),B_High 的新排名将是 3(因为所有 3 个低行动都排名低于 B_High),C_High 的新排名将是 3(因为所有 3 个低级动作的排名都低于 C_High)。

 id   A_High_rank   B_High_rank   C_High_rank   
001             1             3             3                      
002             0             1             1           
003             2             3             0           
004             0             0             2    

我觉得这可以通过 if/else 语句来完成,但怀疑应该有一种更有效的方法来使用 tidyverse 来实现这一点。在真实数据集中,我有 1000 多行和 12 个动作(6 个高和 6 个低)。如果对此有任何帮助,我将不胜感激。

谢谢!

数据:

"id   A_High   B_High   C_High   D_Low   E_Low    F_Low
001       5         2         1      6       4        3
002       6         4         3      5       2        1
003       3         1         6      2       4        5
004       6         5         2      1       3        4"

A base R 选项将遍历 'High' 列,通过检查它是否小于 'Low' 列来获取创建的逻辑矩阵的 rowSums ,并通过将 _rank 附加为后缀

来重命名这些输出
out <- cbind(df1[1], sapply(df1[2:4],
    function(x) rowSums(x < df1[endsWith(names(df1), 'Low')])))
names(out)[-1] <- paste0(names(out)[-1], "_rank")

-输出

out
#  id A_High_rank B_High_rank C_High_rank
#1  1           1           3           3
#2  2           0           1           1
#3  3           2           3           0
#4  4           0           0           2

或使用dplyr

library(dplyr)
df1 %>% 
     transmute(id, across(ends_with('High'), 
        ~  rowSums(. <  select(df1, ends_with('Low'))), .names = '{.col}_rank'))
# id A_High_rank B_High_rank C_High_rank
#1  1           1           3           3
#2  2           0           1           1
#3  3           2           3           0
#4  4           0           0           2

数据

df1 <- structure(list(id = 1:4, A_High = c(5L, 6L, 3L, 6L), B_High = c(2L, 
4L, 1L, 5L), C_High = c(1L, 3L, 6L, 2L), D_Low = c(6L, 5L, 2L, 
1L), E_Low = c(4L, 2L, 4L, 3L), F_Low = c(3L, 1L, 5L, 4L)), 
class = "data.frame", row.names = c(NA, 
-4L))

历经千辛万苦,这就是我想出的tidyverse解决方案。这很有趣!

library(tidyverse)

data %>%
  pivot_longer(cols = ends_with("_High"), names_to = "High Variables", values_to = "High") %>%
  pivot_longer(cols = ends_with("_Low"), names_to = "Low Variables", values_to = "Low") %>%
  filter(High-Low < 0) %>%
  group_by(`High Variables`, `id`) %>%
  summarise(Count = n()) %>%
  pivot_wider(names_from = `High Variables`, values_from = Count) %>%
  arrange(id)

翻译: 前两行创建两对列并保持 id 不变。每对都有两列,一列包含原始列名,另一列包含值。每对列代表 HighLow.

然后,我过滤了所有行,只保留 Low 大于 High 的行。然后我计算每个 id 还剩多少,然后倒转格式。

现在我只需要弄清楚如何将那些 NA 变成 0

这是输出:

> data %>%
+   pivot_longer(cols = ends_with("_High"), names_to = "High Variables", values_to = "High") %>%
+   pivot_longer(cols = ends_with("_Low"), names_to = "Low Variables", values_to = "Low") %>%
+   filter(High < Low) %>%
+   group_by(`High Variables`, `id`) %>%
+   summarise(Count = n()) %>%
+   pivot_wider(names_from = `High Variables`, values_from = Count) %>%
+   arrange(id)
`summarise()` regrouping output by 'High Variables' (override with `.groups` argument)
# A tibble: 4 x 4
     id A_High B_High C_High
  <int>  <int>  <int>  <int>
1     1      1      3      3
2     2     NA      1      1
3     3      2      3     NA
4     4     NA     NA      2