<p>ggplot2 is cool and logical. There isn't an equivalent &quot;cool and logical&quot; for Python.</p>
<p>Here is the answer minus the statistical overlay (library loading issue),</p>
<pre><code>library(ggplot2)
library(dplyr)

# Dummy data
set.seed(123)  # set random seed!!
metadata &lt;- data.frame(
    sex = sample(c(&quot;M&quot;, &quot;F&quot;), 100, replace = TRUE),
    subgroups = sample(c(&quot;1&quot;, &quot;2&quot;, &quot;3&quot;), 100, replace = TRUE),
    air_quality = sample(0:100, 100, replace = TRUE)
)

# Boxplot
p &lt;- ggplot(metadata, aes(x = sex, y = air_quality)) +
    geom_boxplot(aes(fill = subgroups)) +
    labs(x = &quot;Sex&quot;, y = &quot;Air Quality&quot;, title = &quot;Air Quality by Gender and Subgroup&quot;)

# Counts and subgroups
n_data &lt;- metadata %&gt;%
    group_by(sex, subgroups) %&gt;%
    summarise(n = n(), .groups = &quot;drop&quot;) %&gt;%
    group_by(sex) %&gt;%
    mutate(y_pos = max(metadata<span class="math-container">$air_quality[metadata$</span>sex == first(sex)]) + 5)

# Total counts for each gender
total_n &lt;- metadata %&gt;%
    group_by(sex) %&gt;%
    summarise(total = n()) %&gt;%
    mutate(y_pos = max(metadata$air_quality) + 15)  # Position above subgroup counts

# &quot;stack the plot&quot;
p &lt;- p +
    # Subgroup counts
    geom_text(data = n_data,
              aes(x = sex, y = y_pos, label = paste(&quot;n =&quot;, n), group = subgroups),
              position = position_dodge(width = 0.75),
              size = 4,
              color = &quot;red&quot;) +
    # Total counts per gender
    geom_text(data = total_n,
              aes(x = sex, y = y_pos, label = paste(&quot;Group n =&quot;, total)),
              size = 5,
              color = &quot;blue&quot;)

print(p)

# Save it
ggsave(&quot;air_quality_plot.pdf&quot;, p)
</code></pre>
<p><a href="https://i.sstatic.net/TMEhu3QJ.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/TMEhu3QJ.jpg" alt="enter image description here" /></a></p>
<p>Is this ok? I dunno why I'd got a stats library loading issue, but R ... its not my favourite.</p>

CC BY-SA 4.0
2024-12-19T09:36:08.077Z
Thank you very much. However the numbers are going on top of each other, any idea how I could fix this? Thanks again ! (see picture  in original post)
Bine
CC BY-SA 4.0
2024-12-20T15:47:26.280Z
Thank you so much for your answer. I get the following error messagen:Error in `mutate()`:
ℹ In argument: `max_y = +...`.
ℹ In group 1: `sex = "FEMALE"`.
Caused by error in `Summary.factor()`:
! 'max' not meaningful for factors
Bine
CC BY-SA 4.0
2024-12-31T06:56:07.743Z
@Bine done minus the statistical overlay ... stats package issue.
M__
CC BY-SA 4.0
2024-12-31T07:25:00.117Z
Awesome! That's exactly what I was looking for. I added the statistical test in the final solution above. Thanks again for your help very appreciated, happy new year!
Bine
CC BY-SA 4.0
2024-12-19T01:33:11.467Z
2024-12-31T07:06:45.247Z
2024-12-31T07:06:45.247Z
M__
<p>Good afternoon,</p>
<p>I am trying to add the n for each of the subgroups in females and males (not just for the total in male (n=107) and female (n=94)). However, I havent found a way to do so:</p>
<p>The following code is used to generate the plot below:</p>
<pre><code>stat.test &lt;- metadata %&gt;%
  wilcox_test(air_quality ~ sex) %&gt;%
  add_significance()

p1&lt;-ggplot(data = metadata, aes(x=sex, y=air_quality)) + geom_boxplot(aes(fill=subgroups),show.legend = TRUE) 
stat.test &lt;- stat.test %&gt;% add_xy_position(x = &quot;sex&quot;)

p2&lt;-p1+ stat_pvalue_manual(stat.test, label = &quot;p&quot;) +
  scale_y_continuous(expand = expansion(mult = c(0.01, 0.1)))

p3&lt;-p2+ stat_n_text( y.pos =0.05) + labs(x =&quot;Sex&quot;, y = &quot;Air quality&quot;)+labs(title = &quot;Airquality&quot;)
p3
</code></pre>
<p><a href="https://i.sstatic.net/jykYiXeF.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/jykYiXeF.png" alt="enter image description here" /></a></p>
<p>Any idea?</p>
<p>Thanks a lot!</p>
<hr />
<p>Update after adding the suggested solution below from M:</p>
<p><a href="https://i.sstatic.net/MB6MGDKp.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/MB6MGDKp.png" alt="enter image description here" /></a></p>
<hr />
<p>Update 2 with dummy data:</p>
<pre><code>## Create some dummy data

metadata &lt;- data.frame(sex = sample(c(&quot;M&quot;, &quot;F&quot;), 100, replace = TRUE), 
                       subgroups = sample(c(&quot;1&quot;, &quot;2&quot;,&quot;3&quot;), 100, replace = TRUE), 
                       air_quality = sample(0:100,100, replace = TRUE))


# Is air_quality an issue?
metadata<span class="math-container">$air_quality &lt;- as.numeric(as.character(metadata$</span>air_quality))

stat.test &lt;- metadata %&gt;%
  wilcox_test(air_quality ~ sex) %&gt;%
  add_significance()

# boxplot
p1 &lt;- ggplot(data = metadata, aes(x = sex, y = air_quality)) + 
  geom_boxplot(aes(fill = subgroups), show.legend = TRUE)

# p-values
stat.test &lt;- stat.test %&gt;% add_xy_position(x = &quot;sex&quot;)

# p-value plot
p2 &lt;- p1 + stat_pvalue_manual(stat.test, label = &quot;p&quot;) +
  scale_y_continuous(expand = expansion(mult = c(0.01, 0.1)))

# Count data for labels
n_data &lt;- metadata %&gt;%
  group_by(sex, subgroups) %&gt;%
  summarise(n = n(), .groups = 'drop')

# Dynamic stuff
n_data &lt;- n_data %&gt;%
  group_by(sex) %&gt;%
  mutate(max_y = max(metadata<span class="math-container">$air_quality[metadata$</span>sex == sex], na.rm = TRUE) + 0.1)

p3 &lt;- p2 + 
  geom_text(data = n_data, aes(x = sex, y = max_y, label = paste(&quot;n =&quot;, n), group = subgroups),
            position = position_dodge(width = 0.1), size = 3, color = &quot;blue&quot;) +
  labs(x = &quot;Sex&quot;, y = &quot;Air Quality&quot;) + 
  labs(title = &quot;Air Quality by Gender and Subgroup&quot;)

p3
</code></pre>
<p><a href="https://i.sstatic.net/cWPJEkwg.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/cWPJEkwg.png" alt="enter image description here" /></a></p>
<hr />
<p>Update 3 including final solution with statistical test</p>
<pre><code># Dummy data
set.seed(123)  # set random seed!!
metadata &lt;- data.frame(
  sex = sample(c(&quot;M&quot;, &quot;F&quot;), 100, replace = TRUE),
  subgroups = sample(c(&quot;1&quot;, &quot;2&quot;, &quot;3&quot;), 100, replace = TRUE),
  air_quality = sample(0:100, 100, replace = TRUE)
)

stat.test &lt;- metadata %&gt;%
  wilcox_test(air_quality ~ sex) %&gt;%
  add_significance()


# Boxplot
p &lt;- ggplot(metadata, aes(x = sex, y = air_quality)) +
  geom_boxplot(aes(fill = subgroups)) +
  labs(x = &quot;Sex&quot;, y = &quot;Air Quality&quot;, title = &quot;Air Quality by Gender and Subgroup&quot;)

# p-values
stat.test &lt;- stat.test %&gt;% add_xy_position(x = &quot;sex&quot;)

# p-value plot
p &lt;- p + stat_pvalue_manual(stat.test, label = &quot;p&quot;) +
  scale_y_continuous(expand = expansion(mult = c(0.01, 0.1)))

# Counts and subgroups
n_data &lt;- metadata %&gt;%
  group_by(sex, subgroups) %&gt;%
  summarise(n = n(), .groups = &quot;drop&quot;) %&gt;%
  group_by(sex) %&gt;%
  mutate(y_pos = max(metadata<span class="math-container">$air_quality[metadata$</span>sex == first(sex)]) + 5)

# Total counts for each gender
total_n &lt;- metadata %&gt;%
  group_by(sex) %&gt;%
  summarise(total = n()) %&gt;%
  mutate(y_pos = max(metadata$air_quality) + 15)  # Position above subgroup counts

# &quot;stack the plot&quot;
p &lt;- p +
  # Subgroup counts
  geom_text(data = n_data,
            aes(x = sex, y = y_pos, label = paste(&quot;n =&quot;, n), group = subgroups),
            position = position_dodge(width = 0.75),
            size = 4,
            color = &quot;red&quot;) +
  # Total counts per gender
  geom_text(data = total_n,
            aes(x = sex, y = y_pos, label = paste(&quot;Group n =&quot;, total)),
            size = 5,
            color = &quot;blue&quot;)

p
</code></pre>
<p><a href="https://i.sstatic.net/kZaJnkUb.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/kZaJnkUb.png" alt="enter image description here" /></a></p>

<r><ggplot2>
ggplot with stat_n_text with n for subgroups
# ggplot with stat_n_text with n for subgroups

(Asked by: username_0 on 2024-12-18)

Good afternoon,
I am trying to add the n for each of the subgroups in females and males (not just for the total in male (n=107) and female (n=94)). However, I havent found a way to do so:
The following code is used to generate the plot below:
stat.test <- metadata %>%
  wilcox_test(air_quality ~ sex) %>%
  add_significance()

p1<-ggplot(data = metadata, aes(x=sex, y=air_quality)) + geom_boxplot(aes(fill=subgroups),show.legend = TRUE) 
stat.test <- stat.test %>% add_xy_position(x = "sex")

p2<-p1+ stat_pvalue_manual(stat.test, label = "p") +
  scale_y_continuous(expand = expansion(mult = c(0.01, 0.1)))

p3<-p2+ stat_n_text( y.pos =0.05) + labs(x ="Sex", y = "Air quality")+labs(title = "Airquality")
p3


Any idea?
Thanks a lot!

Update after adding the suggested solution below from M:


Update 2 with dummy data:
## Create some dummy data

metadata <- data.frame(sex = sample(c("M", "F"), 100, replace = TRUE), 
                       subgroups = sample(c("1", "2","3"), 100, replace = TRUE), 
                       air_quality = sample(0:100,100, replace = TRUE))


# Is air_quality an issue?
metadata$air_quality <- as.numeric(as.character(metadata$air_quality))

stat.test <- metadata %>%
  wilcox_test(air_quality ~ sex) %>%
  add_significance()

# boxplot
p1 <- ggplot(data = metadata, aes(x = sex, y = air_quality)) + 
  geom_boxplot(aes(fill = subgroups), show.legend = TRUE)

# p-values
stat.test <- stat.test %>% add_xy_position(x = "sex")

# p-value plot
p2 <- p1 + stat_pvalue_manual(stat.test, label = "p") +
  scale_y_continuous(expand = expansion(mult = c(0.01, 0.1)))

# Count data for labels
n_data <- metadata %>%
  group_by(sex, subgroups) %>%
  summarise(n = n(), .groups = 'drop')

# Dynamic stuff
n_data <- n_data %>%
  group_by(sex) %>%
  mutate(max_y = max(metadata$air_quality[metadata$sex == sex], na.rm = TRUE) + 0.1)

p3 <- p2 + 
  geom_text(data = n_data, aes(x = sex, y = max_y, label = paste("n =", n), group = subgroups),
            position = position_dodge(width = 0.1), size = 3, color = "blue") +
  labs(x = "Sex", y = "Air Quality") + 
  labs(title = "Air Quality by Gender and Subgroup")

p3



Update 3 including final solution with statistical test
# Dummy data
set.seed(123)  # set random seed!!
metadata <- data.frame(
  sex = sample(c("M", "F"), 100, replace = TRUE),
  subgroups = sample(c("1", "2", "3"), 100, replace = TRUE),
  air_quality = sample(0:100, 100, replace = TRUE)
)

stat.test <- metadata %>%
  wilcox_test(air_quality ~ sex) %>%
  add_significance()


# Boxplot
p <- ggplot(metadata, aes(x = sex, y = air_quality)) +
  geom_boxplot(aes(fill = subgroups)) +
  labs(x = "Sex", y = "Air Quality", title = "Air Quality by Gender and Subgroup")

# p-values
stat.test <- stat.test %>% add_xy_position(x = "sex")

# p-value plot
p <- p + stat_pvalue_manual(stat.test, label = "p") +
  scale_y_continuous(expand = expansion(mult = c(0.01, 0.1)))

# Counts and subgroups
n_data <- metadata %>%
  group_by(sex, subgroups) %>%
  summarise(n = n(), .groups = "drop") %>%
  group_by(sex) %>%
  mutate(y_pos = max(metadata$air_quality[metadata$sex == first(sex)]) + 5)

# Total counts for each gender
total_n <- metadata %>%
  group_by(sex) %>%
  summarise(total = n()) %>%
  mutate(y_pos = max(metadata$air_quality) + 15)  # Position above subgroup counts

# "stack the plot"
p <- p +
  # Subgroup counts
  geom_text(data = n_data,
            aes(x = sex, y = y_pos, label = paste("n =", n), group = subgroups),
            position = position_dodge(width = 0.75),
            size = 4,
            color = "red") +
  # Total counts per gender
  geom_text(data = total_n,
            aes(x = sex, y = y_pos, label = paste("Group n =", total)),
            size = 5,
            color = "blue")

p

**Tags:** r, ggplot2

---

## 1 answers

### Answer by username_1 on 2024-12-19. Score: 1 | Accepted answer

ggplot2 is cool and logical. There isn't an equivalent "cool and logical" for Python.
Here is the answer minus the statistical overlay (library loading issue),
library(ggplot2)
library(dplyr)

# Dummy data
set.seed(123)  # set random seed!!
metadata <- data.frame(
    sex = sample(c("M", "F"), 100, replace = TRUE),
    subgroups = sample(c("1", "2", "3"), 100, replace = TRUE),
    air_quality = sample(0:100, 100, replace = TRUE)
)

# Boxplot
p <- ggplot(metadata, aes(x = sex, y = air_quality)) +
    geom_boxplot(aes(fill = subgroups)) +
    labs(x = "Sex", y = "Air Quality", title = "Air Quality by Gender and Subgroup")

# Counts and subgroups
n_data <- metadata %>%
    group_by(sex, subgroups) %>%
    summarise(n = n(), .groups = "drop") %>%
    group_by(sex) %>%
    mutate(y_pos = max(metadata$air_quality[metadata$sex == first(sex)]) + 5)

# Total counts for each gender
total_n <- metadata %>%
    group_by(sex) %>%
    summarise(total = n()) %>%
    mutate(y_pos = max(metadata$air_quality) + 15)  # Position above subgroup counts

# "stack the plot"
p <- p +
    # Subgroup counts
    geom_text(data = n_data,
              aes(x = sex, y = y_pos, label = paste("n =", n), group = subgroups),
              position = position_dodge(width = 0.75),
              size = 4,
              color = "red") +
    # Total counts per gender
    geom_text(data = total_n,
              aes(x = sex, y = y_pos, label = paste("Group n =", total)),
              size = 5,
              color = "blue")

print(p)

# Save it
ggsave("air_quality_plot.pdf", p)


Is this ok? I dunno why I'd got a stats library loading issue, but R ... its not my favourite.

> **Comments:**
> **username_0**: Thank you very much. However the numbers are going on top of each other, any idea how I could fix this? Thanks again ! (see picture  in original post)  
> **username_0**: Thank you so much for your answer. I get the following error messagen:Error in `mutate()`: ℹ In argument: `max_y = +...`. ℹ In group 1: `sex = "FEMALE"`. Caused by error in `Summary.factor()`: ! 'max' not meaningful for factors  
> **username_1**: @Bine done minus the statistical overlay ... stats package issue.  
> **username_0**: Awesome! That's exactly what I was looking for. I added the statistical test in the final solution above. Thanks again for your help very appreciated, happy new year!  

---

