#r #dplyr #copy #subset
#r #dplyr #Копировать #подмножество
Вопрос:
У меня есть фрейм данных (простой пример ниже), в котором каждый пользователь заполняет анкету разное количество раз, и каждое заполнение анкеты приводит к появлению строки в фрейме данных. В моем простом примере пользователи A, C и D имеют записи за 5 дней, но пользователь B имеет записи только за 4:
> df
UserId Days_From_First_Use Q1 Q2 Q3
1 A 0 3 2 1
2 A 1 1 0 0
3 A 2 1 1 0
4 A 3 0 2 0
5 A 4 1 1 1
6 B 0 4 8 2
7 B 2 2 2 1
8 B 4 5 6 5
9 B 5 4 5 5
10 C 0 5 7 2
11 C 1 2 2 2
12 C 2 5 5 4
13 C 3 6 5 3
14 C 4 6 6 4
15 D 0 5 3 5
16 D 1 5 3 4
17 D 2 4 2 6
18 D 3 0 0 1
19 D 4 1 1 1
Теперь я вычисляю изменчивость временных рядов для каждого пользователя следующим образом:
> df <- df %>%
group_by(UserId) %>%
mutate(across(all_of(c("Q1", "Q2", "Q3")), sd,.names = paste0("Sigma_", "{.col}"))) %>%
ungroup()
> df
# A tibble: 19 x 8
UserId Days_From_First_Use Q1 Q2 Q3 Sigma_Q1 Sigma_Q2 Sigma_Q3
<fct> <int> <int> <int> <int> <dbl> <dbl> <dbl>
1 A 0 3 2 1 1.10 0.837 0.548
2 A 1 1 0 0 1.10 0.837 0.548
3 A 2 1 1 0 1.10 0.837 0.548
4 A 3 0 2 0 1.10 0.837 0.548
5 A 4 1 1 1 1.10 0.837 0.548
6 B 0 4 8 2 1.26 2.5 2.06
7 B 2 2 2 1 1.26 2.5 2.06
8 B 4 5 6 5 1.26 2.5 2.06
9 B 5 4 5 5 1.26 2.5 2.06
10 C 0 5 7 2 1.64 1.87 1
11 C 1 2 2 2 1.64 1.87 1
12 C 2 5 5 4 1.64 1.87 1
13 C 3 6 5 3 1.64 1.87 1
14 C 4 6 6 4 1.64 1.87 1
15 D 0 5 3 5 2.35 1.30 2.30
16 D 1 5 3 4 2.35 1.30 2.30
17 D 2 4 2 6 2.35 1.30 2.30
18 D 3 0 0 1 2.35 1.30 2.30
19 D 4 1 1 1 2.35 1.30 2.30
Теперь начинается та часть, которая вызывает у меня проблемы: Я хотел бы вычислить медианный sd для всех пользователей, чтобы я мог идентифицировать два подмножества пользователей с sd выше медианы и ниже медианы. Я не могу просто вычислить медианный sd по всем строкам, потому что количество наблюдений для каждого пользователя разное. Однако я могу сгруппировать по Days_From_First_Use
, а затем вычислить медианный sd для каждого дня. Поскольку у всех пользователей есть день 0 (их самый первый день), медианный sd в этот день — это значение, которое я хочу. Итак, я набираю:
> df <- df %>%
group_by(UserId) %>%
mutate(across(all_of(c("Q1", "Q2", "Q3")), sd,.names = paste0("Sigma_", "{.col}"))) %>%
ungroup() %>%
group_by(Days_From_First_Use) %>%
mutate(across(all_of(paste0("Sigma_", c("Q1", "Q2", "Q3"))), median ,.names = paste0("Median_", "{.col}"))) %>%
ungroup()
>
> df
# A tibble: 19 x 11
UserId Days_From_First_Use Q1 Q2 Q3 Sigma_Q1 Sigma_Q2 Sigma_Q3 Median_Sigma_Q1 Median_Sigma_Q2 Median_Sigma_Q3
<fct> <int> <int> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 A 0 3 2 1 1.10 0.837 0.548 1.45 1.59 1.53
2 A 1 1 0 0 1.10 0.837 0.548 1.64 1.30 1
3 A 2 1 1 0 1.10 0.837 0.548 1.45 1.59 1.53
4 A 3 0 2 0 1.10 0.837 0.548 1.64 1.30 1
5 A 4 1 1 1 1.10 0.837 0.548 1.45 1.59 1.53
6 B 0 4 8 2 1.26 2.5 2.06 1.45 1.59 1.53
7 B 2 2 2 1 1.26 2.5 2.06 1.45 1.59 1.53
8 B 4 5 6 5 1.26 2.5 2.06 1.45 1.59 1.53
9 B 5 4 5 5 1.26 2.5 2.06 1.26 2.5 2.06
10 C 0 5 7 2 1.64 1.87 1 1.45 1.59 1.53
11 C 1 2 2 2 1.64 1.87 1 1.64 1.30 1
12 C 2 5 5 4 1.64 1.87 1 1.45 1.59 1.53
13 C 3 6 5 3 1.64 1.87 1 1.64 1.30 1
14 C 4 6 6 4 1.64 1.87 1 1.45 1.59 1.53
15 D 0 5 3 5 2.35 1.30 2.30 1.45 1.59 1.53
16 D 1 5 3 4 2.35 1.30 2.30 1.64 1.30 1
17 D 2 4 2 6 2.35 1.30 2.30 1.45 1.59 1.53
18 D 3 0 0 1 2.35 1.30 2.30 1.64 1.30 1
19 D 4 1 1 1 2.35 1.30 2.30 1.45 1.59 1.53
Для справки, (неправильные) медианы по всему фрейму данных равны 1,64, 1,30 и 1 соответственно, в то время как правильные медианы равны 1,45, 1,59 и 1,53.
Теперь я хочу заменить все медианные sds на sd в день 0. Как только я это сделаю, я смогу правильно разделить фрейм данных на подмножества с высоким sd и низким sd.
Вопрос: Как я могу скопировать правильные медианы дня 0 по этим трем столбцам, а затем создать новые столбцы с подмножествами с низким sd и высоким sd, определенными sd пользователя относительно медианной волатильности для каждого вопроса?
С уважением и заранее большое спасибо
Томас Филипс
Ответ №1:
Я думаю, вы могли бы вычислить правильную медиану для каждого пользователя, используя только первую запись для каждого пользователя, а затем left_join
.
df =
tibble(
UserId = c("A", "A", "A", "A", "A", "B", "B", "B", "B", "C", "C", "C", "C", "C", "D", "D", "D", "D", "D"),
DFFU = c(0, 1, 2, 3, 4, 0, 2, 4, 5, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4),
Q1 = c(3, 1, 1, 0, 1, 4, 2, 5, 4, 5, 2, 5, 6, 6, 5, 5, 4, 0, 1),
Q2 = c(2,0,1,2,1,8,2,6,5,7,2,5,5,6,3,3,2,0,1),
Q3 = c(1,0,0,0,1,2,1,5,5,2,2,4,3,4,5,4,6,1,1)
)
df <- df %>%
group_by(UserId) %>%
mutate(across(all_of(c("Q1", "Q2", "Q3")), sd,.names = paste0("Sigma_", "{.col}"))) %>%
ungroup()
df %>%
filter(DFFU == 0) %>%
transmute(UserId = UserId, across(all_of(paste0("Sigma_", c("Q1", "Q2", "Q3"))), median ,.names = paste0("Median_", "{.col}"))) %>%
{left_join(df, .)}
Получение:
> df
# A tibble: 19 x 11
UserId DFFU Q1 Q2 Q3 Sigma_Q1 Sigma_Q2 Sigma_Q3 Median_Sigma_Q1 Median_Sigma_Q2 Median_Sigma_Q3
<chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 A 0 3 2 1 1.10 0.837 0.548 1.45 1.59 1.53
2 A 1 1 0 0 1.10 0.837 0.548 1.45 1.59 1.53
3 A 2 1 1 0 1.10 0.837 0.548 1.45 1.59 1.53
4 A 3 0 2 0 1.10 0.837 0.548 1.45 1.59 1.53
5 A 4 1 1 1 1.10 0.837 0.548 1.45 1.59 1.53
6 B 0 4 8 2 1.26 2.5 2.06 1.45 1.59 1.53
7 B 2 2 2 1 1.26 2.5 2.06 1.45 1.59 1.53
8 B 4 5 6 5 1.26 2.5 2.06 1.45 1.59 1.53
9 B 5 4 5 5 1.26 2.5 2.06 1.45 1.59 1.53
10 C 0 5 7 2 1.64 1.87 1 1.45 1.59 1.53
11 C 1 2 2 2 1.64 1.87 1 1.45 1.59 1.53
12 C 2 5 5 4 1.64 1.87 1 1.45 1.59 1.53
13 C 3 6 5 3 1.64 1.87 1 1.45 1.59 1.53
14 C 4 6 6 4 1.64 1.87 1 1.45 1.59 1.53
15 D 0 5 3 5 2.35 1.30 2.30 1.45 1.59 1.53
16 D 1 5 3 4 2.35 1.30 2.30 1.45 1.59 1.53
17 D 2 4 2 6 2.35 1.30 2.30 1.45 1.59 1.53
18 D 3 0 0 1 2.35 1.30 2.30 1.45 1.59 1.53
19 D 4 1 1 1 2.35 1.30 2.30 1.45 1.59 1.53
Одна из причин, по которой ваш анализ становится таким странным, заключается в том, что вы нарушаете принципы аккуратных данных. В вашем исходном наборе данных каждая строка представляет один опрос, но стандартное отклонение применяется к каждому учащемуся, а не к каждому опросу. Таким образом, значения стандартного отклонения должны отображаться в таблице с 5 строками, по одной строке для каждого учащегося. Затем медиана представляет совокупность учащихся. Существует только одна совокупность, поэтому должна быть только одна строка. Поэтому я бы рекомендовал:
sd_df <-
df %>%
group_by(UserId) %>%
summarize(
across(
all_of(c("Q1", "Q2", "Q3")),
.fns = sd,
.names = paste0("Sigma_", "{.col}")
)
)
median_sd_df <-
sd_df %>%
summarize(
across(
all_of(paste0("Sigma_", c("Q1", "Q2", "Q3"))),
.fns = median,
.names = paste0("Sigma_", "{.col}")
),
n = n()
)
что дает вам:
> sd_df
# A tibble: 4 x 5
UserId Sigma_Q1 Sigma_Q2 Sigma_Q3 n
<chr> <dbl> <dbl> <dbl> <int>
1 A 1.10 0.837 0.548 5
2 B 1.26 2.5 2.06 4
3 C 1.64 1.87 1 5
4 D 2.35 1.30 2.30 5
> median_sd_df
# A tibble: 1 x 3
Median_Sigma_Q1 Median_Sigma_Q2 Median_Sigma_Q3
<dbl> <dbl> <dbl>
1 1.45 1.59 1.53
Комментарии:
1. К сожалению, я не могу переформулировать проблему — это то, что есть. Стандартное отклонение действительно применяется к каждому субъекту, а не к каждой строке, и количество строк для каждого субъекта по своей сути является переменным: некоторые субъекты заполняют опрос регулярно, а другие нет. Мне был предложен подход summary / left join, но я его не понял, и поэтому я выбрал маршрут, который я сделал. Мне нравится ваш подход, поскольку он чище моего, и я его приму. Спасибо за руководство.
2. @ThomasPhilips, просто чтобы уточнить, я надеюсь, что я не столкнулся с критикой. Вы, конечно, не можете контролировать, как часто люди заполняют опрос! Конечно, тот факт, что размер вашей выборки является переменным, имеет некоторые статистические последствия, но данные такие, какие они есть, и мы все делаем все возможное! Для прозрачности вы могли бы рассмотреть возможность предоставления информации о размере выборки по отдельности при записи результатов. Я изменю свой ответ, чтобы включить эту информацию. HTH!
3. Вы вообще не кажетесь критичным — набор данных такой, какой он есть, и я ничего не могу сделать, чтобы его изменить. На самом деле, поскольку это данные о работоспособности, о которых сообщается самостоятельно, было бы неправильно их изменять,
Ответ №2:
Решаемая проблема, но неуклюжим способом: сначала сгруппируйте по Days_From_First_Use, используйте тот факт, что у нас есть все пользователи в день 0, вычислите медиану, извлеките первую строку в другой фрейм данных, а затем перезапишите все соответствующие строки этим новым фреймом данных.
> df_median_sigma <- df %>%
arrange(Days_From_First_Use) %>%
select(starts_with("Median_Sigma")) %>%
filter(row_number( ) == 1)
> df_median_sigma
# A tibble: 1 x 3
Median_Sigma_Q1 Median_Sigma_Q2 Median_Sigma_Q3
<dbl> <dbl> <dbl>
1 1.45 1.59 1.53
Наконец, перепишите все соответствующие столбцы в df с правильными медианами:
> df[paste0("Median_Sigma_", c("Q1", "Q2", "Q3"))] <- df_median_sigma
> df
# A tibble: 19 x 11
UserId Days_From_First_Use Q1 Q2 Q3 Sigma_Q1 Sigma_Q2 Sigma_Q3 Median_Sigma_Q1 Median_Sigma_Q2 Median_Sigma_Q3
<fct> <int> <int> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 A 0 3 2 1 1.10 0.837 0.548 1.45 1.59 1.53
2 A 1 1 0 0 1.10 0.837 0.548 1.45 1.59 1.53
3 A 2 1 1 0 1.10 0.837 0.548 1.45 1.59 1.53
4 A 3 0 2 0 1.10 0.837 0.548 1.45 1.59 1.53
5 A 4 1 1 1 1.10 0.837 0.548 1.45 1.59 1.53
6 B 0 4 8 2 1.26 2.5 2.06 1.45 1.59 1.53
7 B 2 2 2 1 1.26 2.5 2.06 1.45 1.59 1.53
8 B 4 5 6 5 1.26 2.5 2.06 1.45 1.59 1.53
9 B 5 4 5 5 1.26 2.5 2.06 1.45 1.59 1.53
10 C 0 5 7 2 1.64 1.87 1 1.45 1.59 1.53
11 C 1 2 2 2 1.64 1.87 1 1.45 1.59 1.53
12 C 2 5 5 4 1.64 1.87 1 1.45 1.59 1.53
13 C 3 6 5 3 1.64 1.87 1 1.45 1.59 1.53
14 C 4 6 6 4 1.64 1.87 1 1.45 1.59 1.53
15 D 0 5 3 5 2.35 1.30 2.30 1.45 1.59 1.53
16 D 1 5 3 4 2.35 1.30 2.30 1.45 1.59 1.53
17 D 2 4 2 6 2.35 1.30 2.30 1.45 1.59 1.53
18 D 3 0 0 1 2.35 1.30 2.30 1.45 1.59 1.53
19 D 4 1 1 1 2.35 1.30 2.30 1.45 1.59 1.53
Работает, но немного неуклюже. Я подозреваю, что у dplyr есть более элегантный способ сделать это, но я не смог его найти.