【问题标题】:Making a plot of categorical data with two predictors使用两个预测变量绘制分类数据图
【发布时间】:2026-02-05 17:10:01
【问题描述】:

这个问题是我问的previous one 的扩展,数据稍微复杂一些。这似乎很基本,但我已经为此苦苦挣扎了好几天。

我需要通过自变量 ses(x 轴)和 agegroup(可能是堆叠的条形图分组)创建因变量 (choice) 的流行百分比图。理想情况下,我希望情节是一个并排的 2 面情节,每个性别一个面。

我数据的相关部分是这样的形式:

subject   choice       agegroup    sex       ses

John      square       2           Female    A
John      triangle     2           Female    A
John      triangle     2           Female    A
Mary      circle       2           Female    C
Mary      square       2           Female    C
Mary      rectangle    2           Female    C
Mary      square       2           Female    C
Hodor     hodor        5           Male      D
Hodor     hodor        5           Male      D
Hodor     hodor        5           Male      D
Hodor     hodor        5           Male      D
Jill      square       3           Female    B
Jill      circle       3           Female    B
Jill      square       3           Female    B
Jill      hodor        3           Female    B
Jill      triangle     3           Female    B
Jill      rectangle    3           Female    B
... [about 12,000 more observations follow]

我想使用ggplot2 是因为它的强大功能和灵活性,以及​​它明显的易用性。但我发现的每个教程或操作指南都从 90% 的工作开始,因为它们只是加载了 R 或其包提供的内置数据集之一。但我当然需要使用自己的数据。

我知道需要将其转换为长格式以便ggplot2 能够使用它,但我只是无法正确地做到这一点。而且,我对市面上所有不同的数据处理包以及其中一些似乎是其他包的一部分或类似的东西感到更加困惑。

编辑:我开始意识到,根据我最初的问题,用线图绘制它是行不通的。至少我现在不这么认为。所以这里有一个绘制这个数据集的可能方式的模型(具有完全虚构的值):

颜色代表对choice的不同响应。

有人可以帮我解决这个问题吗?如果您对更好的数据可视化方法有任何建议,请分享!

【问题讨论】:

  • 尚不清楚主题或主题列是否有任何相关性或可能被删除。
  • 我的理解是,应该根据每个主题的平均值来计算总体百分比,而不是每个自变量组合的所有观察值,否则结果将是偏向于我有更多观察的人。所以我认为 subject 应该在计算中使用。 ses 本身是相关的,但从这里的数据中并不清楚。
  • @GilWilliams “...通过自变量 sel(x 轴)...”在您的问题中。什么是“选择”?
  • @AndrewLavers sel 应该是ses!在这种情况下是社会经济地位。

标签: r ggplot2 categorical-data


【解决方案1】:

这显示了修改后的问题的点状图和堆积条形图。思考可视化的一些指导:您是否已经知道数据中的“故事”?如果没有,那么您可能需要通过许多可视化来发现故事,构建最能展示故事的可视化。


df <- read.table(text='subject choice agegroup sex ses                                      
John square 2 Female A                                                                      
John triangle 2 Female A                                                                    
John triangle 2 Female A                                                                    
Mary circle 2 Female C                                                                      
Mary square 2 Female C                                                                      
Mary rectangle 2 Female C                                                                   
Mary square 2 Female C                                                                      
Hodor hodor 5 Male D                                                                        
Hodor hodor 5 Male D                                                                        
Hodor hodor 5 Male D                                                                        
Hodor hodor 5 Male D                                                                        
Jill square 3 Female B                                                                      
Jill circle 3 Female B                                                                      
Jill square 3 Female B                                                                      
Jill hodor 3 Female B                                                                       
Jill triangle 3 Female B                                                                    
Jill rectangle 3 Female B', header=TRUE)                                                    

library(tidyverse)                                                                          
#> ── Attaching packages ──────────────────────────────────────────────────────── tidyverse 1.2.1 ──
#> ✔ ggplot2 2.2.1     ✔ purrr   0.2.4
#> ✔ tibble  1.4.2     ✔ dplyr   0.7.4
#> ✔ tidyr   0.8.0     ✔ stringr 1.3.0
#> ✔ readr   1.1.1     ✔ forcats 0.3.0
#> ── Conflicts ─────────────────────────────────────────────────────────── tidyverse_conflicts() ──
#> ✖ dplyr::filter() masks stats::filter()
#> ✖ dplyr::lag()    masks stats::lag()

# agegroup is read as numeric - convert to a factor                                         
df$agegroup <- factor(df$agegroup)                                                          

# Create dataframe by subject (check for data issues!!)                                     
df_subject <- df %>%                                                                        
group_by(subject, agegroup, ses, sex) %>%                                                   
summarize()                                                                                 
df_subject                                                                                  
#> # A tibble: 4 x 4
#> # Groups:   subject, agegroup, ses [?]
#>   subject agegroup ses   sex   
#>   <fct>   <fct>    <fct> <fct> 
#> 1 Hodor   5        D     Male  
#> 2 Jill    3        B     Female
#> 3 John    2        A     Female
#> 4 Mary    2        C     Female

# calculate the proportionate choice by subject                                             
df_subject_choice <- df %>%                                                                 
# summarize the counts by the finest group to analyze                                       
group_by(subject, choice) %>%                                                               
summarize(n=n()) %>%                                                                        
# calculate proportions based on counts                                                     
mutate(p=prop.table(n))                                                                     
df_subject_choice                                                                           
#> # A tibble: 11 x 4
#> # Groups:   subject [4]
#>    subject choice        n     p
#>    <fct>   <fct>     <int> <dbl>
#>  1 Hodor   hodor         4 1.00 
#>  2 Jill    circle        1 0.167
#>  3 Jill    hodor         1 0.167
#>  4 Jill    rectangle     1 0.167
#>  5 Jill    square        2 0.333
#>  6 Jill    triangle      1 0.167
#>  7 John    square        1 0.333
#>  8 John    triangle      2 0.667
#>  9 Mary    circle        1 0.250
#> 10 Mary    rectangle     1 0.250
#> 11 Mary    square        2 0.500

# Put the results together by joining                                                       
df_joined <- df_subject_choice %>%                                                          
left_join(df_subject, by = "subject") %>%                                                   
select(subject, ses, sex, agegroup, choice, p)                                              
df_joined                                                                                   
#> # A tibble: 11 x 6
#> # Groups:   subject [4]
#>    subject ses   sex    agegroup choice        p
#>    <fct>   <fct> <fct>  <fct>    <fct>     <dbl>
#>  1 Hodor   D     Male   5        hodor     1.00 
#>  2 Jill    B     Female 3        circle    0.167
#>  3 Jill    B     Female 3        hodor     0.167
#>  4 Jill    B     Female 3        rectangle 0.167
#>  5 Jill    B     Female 3        square    0.333
#>  6 Jill    B     Female 3        triangle  0.167
#>  7 John    A     Female 2        square    0.333
#>  8 John    A     Female 2        triangle  0.667
#>  9 Mary    C     Female 2        circle    0.250
#> 10 Mary    C     Female 2        rectangle 0.250
#> 11 Mary    C     Female 2        square    0.500

# Summarize to whatever level to analyze (Note that this may be possible directly in ggplot)
df_summary <- df_joined %>%                                                                 
group_by(agegroup, ses, sex, choice) %>%                                                    
summarize(p_mean = mean(p))                                                                 
df_summary                                                                                  
#> # A tibble: 11 x 5
#> # Groups:   agegroup, ses, sex [?]
#>    agegroup ses   sex    choice    p_mean
#>    <fct>    <fct> <fct>  <fct>      <dbl>
#>  1 2        A     Female square     0.333
#>  2 2        A     Female triangle   0.667
#>  3 2        C     Female circle     0.250
#>  4 2        C     Female rectangle  0.250
#>  5 2        C     Female square     0.500
#>  6 3        B     Female circle     0.167
#>  7 3        B     Female hodor      0.167
#>  8 3        B     Female rectangle  0.167
#>  9 3        B     Female square     0.333
#> 10 3        B     Female triangle   0.167
#> 11 5        D     Male   hodor      1.00

# Plot points                                                                               
ggplot(df_summary, aes(x = ses, y = choice, color = agegroup, size = p_mean)) +             
geom_point() +                                                                              
facet_wrap(~sex)                                                                            

# Plot faceted 100% stacked bar                                                             
ggplot(df_summary, aes(x = agegroup, y = p_mean, color = choice, fill=choice)) +            
geom_col() +                                                                                
facet_grid(sex~ses)                                                                         

【讨论】:

  • 这是一个有趣的方法!不幸的是,根据我的真实数据,结果几乎无法解释(参见:imgur.com/88WQuJJ)。关于替代情节技术的任何想法?我的堆叠条形图想法(参见编辑后的问题)可以实现吗?
  • 更新了多面堆积条,关于点图,它看起来像是在相同的 x/y 位置上绘制不同颜色的点。您可以尝试geom_jitter 而不是geom_point(),它会轻推这些点,这样它们就不会过度绘制——也许这样会更容易理解出了什么问题。此外,请仔细检查摘要并确保 df_subject 数据框中的每个主题只有一行。像all(df_subject$subject == unique(df_subject$subject)) 这样的东西应该给出 TRUE 的结果
  • 如此接近我所需要的!但是,有一个悬而未决的问题,那就是在我的真实数据中,最终图的值大于 1.00。这是一个示例:imgur.com/a/BnNVbgh据我所知,在逐行运行代码并检查后,问题出现在最后的ggplot 代码行中。但我不得不承认,我在调试这个问题时遇到了麻烦,因为无法直接访问像 np 这样的东西来检查它们的内容。
  • 1) 我相信现在定义的计算方式(每个科目选择比例的平均值)在堆叠时会产生 > 1 的值。也许修改该定义或尝试geom_col(position="dodge") 取消堆叠。 2)您可以通过打印数据框来查看所有值。您还可以在管道运算符%&gt;% 之前执行语句的每个部分以查看中间结果。 3)因为 df_summary 被分组到与绘图使用的相同的 4 个变量中,所以 ggplot 正在做什么应该不神秘。
【解决方案2】:

不确定我是否正确理解了您想要的输出。所以这是第一次尝试

library( tidyverse )

df2 <- df %>% 
  mutate( agegroup = as.factor( agegroup ) ) %>%
  group_by( ses, agegroup, sex, choice ) %>%
  summarise( count = n() )

#   ses   agegroup sex    choice    count
#   <fct> <fct>    <fct>  <fct>     <int>
# 1 A     2        Female square        1
# 2 A     2        Female triangle      2
# 3 B     3        Female circle        1
# 4 B     3        Female hodor         1
# 5 B     3        Female rectangle     1
# 6 B     3        Female square        2
# 7 B     3        Female triangle      1
# 8 C     2        Female circle        1
# 9 C     2        Female rectangle     1
# 10 C     2        Female square        2
# 11 D     5        Male   hodor         4

ggplot(df2, aes( x = ses, y = count, group=agegroup, colour = agegroup)) +
  geom_point( stat='summary', fun.y=sum) +
  stat_summary(fun.y=sum, geom="line") + 
  facet_grid( c("choice", "sex" ) )

【讨论】:

  • 您发布的第一个版本以facet_wrap( ~sex ) 结尾,是我在视觉上寻找的。这里的代码似乎有两个问题——(1) 显示的是计数而不是百分比,(2) 绘制的图表似乎是每个 ses 的观察总数 choice,而不是count中的回复。
  • 现在我已经考虑了更多,我开始认为我需要显示的内容不能用折线图显示。如果choice 有五个响应(在我的真实数据中,它可以有 2 到 17 之间的任意值,具体取决于测量的内容),我如何通过sesagegroup 绘制每个响应的百分比(然后通过sex)?我的神经元要着火了。
  • @GilWilliams 也许您可以尝试勾勒出您的情节应该是什么样子?你想怎么分组?您希望在哪些组的总数(总和)中显示百分比?
  • 我想在每个不同的agegroups 中显示seschoice 的每个不同响应的百分比(例如三角形= 27%、正方形= 12% 等),每个性别有一个方面。人类可读格式的数据表在这里:i.stack.imgur.com/8QFjf.png(不同之处在于,choice 包含颜色,而这里包含形状)。