Check out my population pyramid:

with your generated data you could do this:
# import the packages in an elegant way ####
packages <- c("tidyverse")
installed_packages <- packages %in% rownames(installed.packages())
if (any(installed_packages == FALSE)) {
install.packages(packages[!installed_packages])
}
invisible(lapply(packages, library, character.only = TRUE))
# _________________________________________________________
# create data ####
sex_age <- data.frame(age=rnorm(n = 10000, mean = 50, sd = 9), sex=c(1, 2)))
# _________________________________________________________
# prepare data + build the plot ####
sex_age %>%
mutate(sex = ifelse(sex == 1, "Male",
ifelse(sex == 2, "Female", NA))) %>% # construct from the sex variable: "Male","Female"
select(age, sex) %>% # pick just the two variables
table() %>% # table it
as.data.frame.matrix() %>% # create data frame matrix
rownames_to_column("age") %>% # rownames are now the age variable
mutate(across(everything(), as.numeric),
# mutate everything as.numeric()
age = ifelse(
# create age groups 5 year steps
age >= 18 & age <= 22 ,
"18-22",
ifelse(
age >= 23 & age <= 27,
"23-27",
ifelse(
age >= 28 & age <= 32,
"28-32",
ifelse(
age >= 33 & age <= 37,
"33-37",
ifelse(
age >= 38 & age <= 42,
"38-42",
ifelse(
age >= 43 & age <= 47,
"43-47",
ifelse(
age >= 48 & age <= 52,
"48-52",
ifelse(
age >= 53 & age <= 57,
"53-57",
ifelse(
age >= 58 & age <= 62,
"58-62",
ifelse(
age >= 63 & age <= 67,
"63-67",
ifelse(
age >= 68 & age <= 72,
"68-72",
ifelse(
age >= 73 & age <= 77,
"73-77",
ifelse(age >= 78 &
age <= 82, "78-82", "83 and older")
)
)
)
)
)
)
)
)
)
)
)
)) %>%
group_by(age) %>% # group by the age
summarize(Female = sum(Female), # summarize the sum of each sex
Male = sum(Male)) %>%
pivot_longer(names_to = 'sex',
# pivot longer
values_to = 'Population',
cols = 2:3) %>%
mutate(
# create a pop perc and a signal 1 / -1
PopPerc = case_when(
sex == 'Male' ~ round(Population / sum(Population) * 100, 2),
TRUE ~ -round(Population / sum(Population) *
100, 2)
),
signal = case_when(sex == 'Male' ~ 1,
TRUE ~ -1)
) %>%
ggplot() + # build the plot with ggplot2
geom_bar(aes(x = age, y = PopPerc, fill = sex), stat = 'identity') + # define aesthetics
geom_text(aes(
# create the text
x = age,
y = PopPerc + signal * .3,
label = abs(PopPerc)
)) +
coord_flip() + # flip the plot
scale_fill_manual(name = '', values = c('darkred', 'steelblue')) + # define the colors (darkred = female, steelblue = male)
scale_y_continuous(
# scale the y-lab
breaks = seq(-10, 10, 1),
labels = function(x) {
paste(abs(x), '%')
}
) +
labs(
# name the labs
x = '',
y = 'Participants in %',
title = 'Population Pyramid',
subtitle = paste0('N = ', nrow(sex_age)),
caption = 'Source: '
) +
theme(
# costume the theme
axis.text.x = element_text(vjust = .5),
panel.grid.major.y = element_line(color = 'lightgray', linetype =
'dashed'),
legend.position = 'top',
legend.justification = 'center'
) +
theme_classic() # choose theme