У меня есть фрейм данных (простой пример ниже), в котором каждый пользователь заполняет анкету разное количество раз, и каждое заполнение анкеты приводит к строке в фрейме данных. В моем простом примере у пользователей 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.
Теперь я хочу заменить все медианы sd на sd в день 0. Как только я это сделаю, я смогу правильно разделить фрейм данных на подмножества с высоким sd и низким sd.
Вопрос: Как мне скопировать правильные медианы дня 0 в эти три столбца, а затем создать новые столбцы с подмножествами Low sd и High sd, определенными sd пользователя относительно средней волатильности для каждого вопроса?
С уважением и заранее с большой благодарностью
Томас Филипс
Я думаю, вы могли бы вычислить правильную медиану для каждого пользователя, используя только первую запись для каждого пользователя, а затем 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
@ThomasPhilips, просто чтобы уточнить, надеюсь, я не был критичен. Вы, конечно, не можете контролировать, как часто люди заполняют опрос! Конечно, тот факт, что размер вашей выборки изменчив, имеет некоторые статистические последствия, но данные таковы, каковы они есть, и мы все делаем все возможное! Для прозрачности вы можете рассмотреть возможность сообщения размера вашей выборки по отдельным лицам, когда вы записываете свои результаты. Я изменю свой ответ, чтобы включить эту информацию. ХТХ!
Вы совсем не кажетесь критичными - набор данных такой, какой он есть, и я ничего не могу сделать, чтобы его изменить. На самом деле, поскольку это данные о состоянии здоровья, сообщаемые самими людьми, было бы неправильно их изменять.
Решил это, но неуклюжим способом: сначала сгруппировать по 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 есть более элегантный способ сделать это, но я не смог его найти.
К сожалению, я не могу переформулировать проблему - она такая, какая есть. Стандартное отклонение действительно применяется к каждому субъекту, а не к каждой строке, и количество строк для каждого субъекта по своей природе является переменным: некоторые субъекты регулярно заполняют опрос, а другие нет. Мне был предложен подход с суммированием / левым соединением, но я его не понял, и поэтому я выбрал маршрут, который я сделал. Мне нравится ваш подход, так как он чище моего, и я его приму. Спасибо за руководство.