2010-12-30 6 views
21

Ich muss ein Pyramidendiagramm zeichnen, wie das anbei.Zeichnung Pyramidendiagramm mit R und ggplot2

alt text

Ich fand ein Beispiel unter Verwendung von R (aber ggplot nicht) von here kann mir auf, dies zu tun mit ggplot einen Hinweis jemand geben? Vielen Dank!

+0

entdeckt gerade eine Funktion mit einem ähnlichen Konzept in 'Hmisc'. 'histbackback (rnorm (20), rnorm (30))'. –

Antwort

19

Dies ist im Wesentlichen ein Back-to-Back-BarPlot, so etwas wie die ggplot2 in der ausgezeichneten learnr Blog generiert diejenigen mit: http://learnr.wordpress.com/2009/09/24/ggplot2-back-to-back-bar-charts/

Sie coord_flip mit einem dieser Grundstücke verwenden, aber ich bin nicht sicher, wie Sie erhalten es, um die y-Achsenbeschriftungen zwischen den beiden Plots wie oben zu teilen. Der folgende Code sollte erhalten Sie nah genug an das Original:

zunächst eine Beispieldatenrahmen von Daten erstellen, konvertieren Sie die Spalte Alter auf einen Faktor mit den erforderlichen Break-Punkte:

require(ggplot2) 
df <- data.frame(Type = sample(c('Male', 'Female', 'Female'), 1000, replace=TRUE), 
       Age = sample(18:60, 1000, replace=TRUE)) 

AgesFactor <- ordered(cut(df$Age, breaks = c(18,seq(20,60,5)), 
          include.lowest = TRUE)) 

df$Age <- AgesFactor 

Starten Sie nun das Gebäude Parzelle: die männlichen und weibliche Plots mit der entsprechenden Teilmenge der Daten, zu unterdrücken Legenden erstellen usw.

gg <- ggplot(data = df, aes(x=Age)) 

gg.male <- gg + 
    geom_bar(subset = .(Type == 'Male'), 
      aes(y = ..count../sum(..count..), fill = Age)) + 
    scale_y_continuous('', formatter = 'percent') + 
    opts(legend.position = 'none') + 
    opts(axis.text.y = theme_blank(), axis.title.y = theme_blank()) + 
    opts(title = 'Male', plot.title = theme_text(size = 10)) + 
    coord_flip()  

für das weibliche Stück in umgekehrten ‚Percent‘ Achse trans = "reverse" Verwendung ...

gg.female <- gg + 
    geom_bar(subset = .(Type == 'Female'), 
      aes(y = ..count../sum(..count..), fill = Age)) + 
    scale_y_continuous('', formatter = 'percent', trans = 'reverse') + 
    opts(legend.position = 'none') + 
    opts(axis.text.y = theme_blank(), 
     axis.title.y = theme_blank(), 
     title = 'Female') + 
    opts(plot.title = theme_text(size = 10)) + 
    coord_flip() 

Jetzt ein Grundstück schafft nur die alters Klammern angezeigt geom_text verwenden, sondern auch eine Dummy-geom_bar verwenden, um sicherzustellen, dass die Skalierung der „Alter“ Achse in diesem Diagramm zu dem in den männlichen und weiblichen Plots identisch ist:

gg.ages <- gg + 
    geom_bar(subset = .(Type == 'Male'), aes(y = 0, fill = alpha('white',0))) + 
    geom_text(aes(y = 0, label = as.character(Age)), size = 3) + 
    coord_flip() + 
    opts(title = 'Ages', 
     legend.position = 'none' , 
     axis.text.y = theme_blank(), 
     axis.title.y = theme_blank(), 
     axis.text.x = theme_blank(), 
     axis.ticks = theme_blank(),   
     plot.title = theme_text(size = 10))  

Schließlich ordnen Sie die Plots auf einem Gitter, das Verfahren in Hadley Wickham Buch mit:

grid.newpage() 

pushViewport(viewport(layout = grid.layout(1,3, widths = c(.4,.2,.4)))) 

vplayout <- function(x, y) viewport(layout.pos.row = x, layout.pos.col = y) 

print(gg.female, vp = vplayout(1,1)) 
print(gg.ages, vp = vplayout(1,2)) 
print(gg.male, vp = vplayout(1,3)) 

alt text

+0

Wie kann ich die Größe der Beschriftungen für Altersklassen erhöhen, ohne die Ausrichtung von linkem und rechtem Plot zu verändern? –

11

Eine leichte zwicken:

library(ggplot2) 
library(plyr) 
library(gridExtra) 

## The Data 
df <- data.frame(Type = sample(c('Male', 'Female', 'Female'), 1000, replace=TRUE), 
    Age = sample(18:60, 1000, replace=TRUE)) 

AgesFactor <- ordered(cut(df$Age, breaks = c(18,seq(20,60,5)), 
    include.lowest = TRUE)) 

df$Age <- AgesFactor 

## Plotting 
gg <- ggplot(data = df, aes(x=Age)) 

gg.male <- gg + 
    geom_bar(data=subset(df,Type == 'Male'), 
     aes(y = ..count../sum(..count..), fill = Age)) + 
    scale_y_continuous('', labels = scales::percent) + 
    theme(legend.position = 'none', 
     axis.title.y = element_blank(), 
     plot.title = element_text(size = 11.5), 
     plot.margin=unit(c(0.1,0.2,0.1,-.1),"cm"), 
     axis.ticks.y = element_blank(), 
     axis.text.y = theme_bw()$axis.text.y) + 
    ggtitle("Male") + 
    coord_flip()  

gg.female <- gg + 
    geom_bar(data=subset(df,Type == 'Female'), 
     aes(y = ..count../sum(..count..), fill = Age)) + 
    scale_y_continuous('', labels = scales::percent, 
        trans = 'reverse') + 
    theme(legend.position = 'none', 
     axis.text.y = element_blank(), 
     axis.ticks.y = element_blank(), 
     plot.title = element_text(size = 11.5), 
     plot.margin=unit(c(0.1,0,0.1,0.05),"cm")) + 
    ggtitle("Female") + 
    coord_flip() + 
    ylab("Age") 

## Plutting it together 
grid.arrange(gg.female, 
    gg.male, 
    widths=c(0.4,0.6), 
    ncol=2 
) 

enter image description here

ich noch mit Margen ein bisschen mehr (vielleicht auch helfen würde panel.margin im theme Anruf) spielen möchte.

+1

Viel besser, thnx. Scheint, dass der 'opts()' Aufruf veraltet ist und die Verwendung von 'theme()' in diesen Tagen gültig ist. –

+0

@Ben Ich habe hier eine Änderung vorgenommen, um deine Antwort auf die neueren Änderungen von 'ggplot2' zu verschieben. Ich habe Prasad Chalasanis Antwort auch direkt integriert, anstatt davon aufzubauen. Wenn Sie es nicht mögen, wenden Sie sich bitte zurück. –

+1

Ich erhalte einen Fehler. 'Fehler: Unbekannte Parameter: Teilmenge'. Ich vermute, es ist von Zeile 18; 'geom_bar (subset =. (Typ == 'Männlich')'. Ist das eine veraltete Syntax? Ich verwende R 3.3.0 und ggplot2 2.1.0 – user5359531

10

Ich habe es mit einer kleinen Problemumgehung gemacht - anstatt geom_bar zu verwenden, habe ich geom_linerange und geom_label benutzt.

library(magrittr) 
library(dplyr) 
library(ggplot2) 

population <- read.csv("https://raw.githubusercontent.com/andriy-gazin/datasets/master/ageSexDistribution.csv") 

population %<>% 
    tidyr::gather(sex, number, -year, - ageGroup) %>% 
    mutate(ageGroup = gsub("100 і старше", "≥100", ageGroup), 
    ageGroup = factor(ageGroup, 
         ordered = TRUE, 
         levels = c("0-4", "5-9", "10-14", "15-19", "20-24", 
            "25-29", "30-34", "35-39", "40-44", 
            "45-49", "50-54", "55-59", "60-64", 
            "65-69", "70-74", "75-79", "80-84", 
            "85-89", "90-94", "95-99", "≥100")), 
    number = ifelse(sex == "male", number*-1/10^6, number/10^6)) %>% 
    filter(year %in% c(1990, 1995, 2000, 2005, 2010, 2015)) 

png(filename = "~/R/pyramid.png", width = 900, height = 1000, type = "cairo") 

ggplot(population, aes(x = ageGroup, color = sex))+ 
    geom_linerange(data = population[population$sex=="male",], 
       aes(ymin = -0.3, ymax = -0.3+number), size = 3.5, alpha = 0.8)+ 
    geom_linerange(data = population[population$sex=="female",], 
       aes(ymin = 0.3, ymax = 0.3+number), size = 3.5, alpha = 0.8)+ 
    geom_label(aes(x = ageGroup, y = 0, label = ageGroup, family = "Ubuntu Condensed"), 
     inherit.aes = F, 
     size = 3.5, label.padding = unit(0.0, "lines"), label.size = 0, 
     label.r = unit(0.0, "lines"), fill = "#EFF2F4", alpha = 0.9, color = "#5D646F")+ 
    scale_y_continuous(breaks = c(c(-2, -1.5, -1, -0.5, 0) + -0.3, c(0, 0.5, 1, 1.5, 2)+0.3), 
       labels = c("2", "1.5", "1", "0.5", "0", "0", "0.5", "1", "1.5", "2"))+ 
    facet_wrap(~year, ncol = 2)+ 
    coord_flip()+ 
labs(title = "Піраміда населення України", 
    subtitle = "Статево-вікові групи у 1990-2015 роках, млн осіб", 
    caption = "Дані: Держкомстат України")+ 
    scale_color_manual(name = "", values = c(male = "#3E606F", female = "#8C3F4D"), 
       labels = c("жінки", "чоловіки"))+ 
    theme_minimal(base_family = "Ubuntu Condensed")+ 
theme(text = element_text(color = "#3A3F4A"), 
    panel.grid.major.y = element_blank(), 
    panel.grid.minor = element_blank(), 
    panel.grid.major.x = element_line(linetype = "dotted", size = 0.3, color = "#3A3F4A"), 
    axis.title = element_blank(), 
    plot.title = element_text(face = "bold", size = 36, margin = margin(b = 10), hjust = 0.030), 
    plot.subtitle = element_text(size = 16, margin = margin(b = 20), hjust = 0.030), 
    plot.caption = element_text(size = 14, margin = margin(b = 10, t = 50), color = "#5D646F"), 
    axis.text.x = element_text(size = 12, color = "#5D646F"), 
    axis.text.y = element_blank(), 
    strip.text = element_text(color = "#5D646F", size = 18, face = "bold", hjust = 0.030), 
    plot.background = element_rect(fill = "#EFF2F4"), 
    plot.margin = unit(c(2, 2, 2, 2), "cm"), 
    legend.position = "top", 
    legend.margin = unit(0.1, "lines"), 
    legend.text = element_text(family = "Ubuntu Condensed", size = 14), 
    legend.text.align = 0) 

dev.off() 

und hier ist die resultierende Handlung:

and here's the resulting plot

+0

Dies ist eine ausgezeichnete Demonstration, wie man ein 'ggplot2'-Diagramm verfeinert Vielen Dank für Ihre Mitteilung – Uwe

+0

Mit der aktuellen Ausgabe von ggplot2 sollte 'legend.margin'' legend.spacing' sein. – Tavrock

0

I @ andriy des Plots genug gemocht eine vereinfachte benutzerdefinierte Funktion aus ihm zu machen:

Daten wie folgt aussehen sollte, und ageGroup ein geordneter Faktor sein.

head(population) 
# ageGroup sex number 
# 1  0-4 male 1.896459 
# 2  5-9 male 1.914255 
# 3 10-14 male 1.832594 
# 4 15-19 male 1.849453 
# 5 20-24 male 1.658733 
# 6 25-29 male 1.918060 

Dann bieten Sie die Daten und die Pausen:

pyramid(population,c(0, 0.5, 1, 1.5, 2)) 

Bei Bedarf kann die Schaffung von Altersgruppen durch Funktion erfolgen age_cat, dass ich von this blog nahm. Siehe Code unten. Ich habe den ursprünglichen Namen und die Standardparameter leicht bearbeitet.

Zum Beispiel:

age_column <- sample(0:110,10000,TRUE) 
table(age_cat(age_column)) 
# 0-9 10-19 20-29 30-39 40-49 50-59 60-69 70-79 80-89 90-99 100+ 
# 885 836 885 927 942 953 886 882 935 872 997 

Funktionen

pyramid <- function(data,.breaks){ 
ggplot(data, aes(x = ageGroup, color = sex))+ 
    geom_linerange(data = data[data$sex=="male",], 
       aes(ymin = -tail(.breaks,1)/7, ymax = -tail(.breaks,1)/7-number), size = 3.5, alpha = 0.8)+ 
    geom_linerange(data = data[data$sex=="female",], 
       aes(ymin = tail(.breaks,1)/7, ymax = tail(.breaks,1)/7+number), size = 3.5, alpha = 0.8)+ 
    geom_label(aes(x = ageGroup, y = 0, label = ageGroup), 
      inherit.aes = F, 
      size = 3.5, label.padding = unit(0.0, "lines"), label.size = NA, 
      label.r = unit(0.0, "lines"), fill = "white", alpha = 0.9, color = "#5D646F")+ 
    scale_y_continuous(breaks = c(-rev(.breaks) -tail(.breaks,1)/7, .breaks+tail(.breaks,1)/7), 
        labels = c(rev(.breaks),.breaks))+ 
    coord_flip()+ 
    scale_color_manual(name = "", values = c(male = "#3E606F", female = "#8C3F4D"))+ 
    theme_minimal()+ 
    theme(text = element_text(color = "#3A3F4A"), 
     panel.grid.major.y = element_blank(), 
     panel.grid.minor = element_blank(), 
     panel.grid.major.x = element_line(linetype = "dotted", size = 0.3, color = "#3A3F4A"), 
     axis.title = element_blank(), 
     axis.text.x = element_text(size = 12, color = "#5D646F"), 
     axis.text.y = element_blank(), 
     strip.text = element_text(color = "#5D646F", size = 18, face = "bold", hjust = 0.030), 
     legend.position = "none") 
} 

age_cat <- function(x, lower = 0, upper = 100, by = 5, 
        sep = "-", above.char = "+") { 

    labs <- c(paste(seq(lower, upper - by, by = by), 
        seq(lower + by - 1, upper - 1, by = by), 
        sep = sep), 
      paste(upper, above.char, sep = "")) 

    cut(floor(x), breaks = c(seq(lower, upper, by = by), Inf), 
     right = FALSE, labels = labs) 
} 

Daten

library(dplyr) 
library(ggplot2) 
population <- read.csv("https://raw.githubusercontent.com/andriy-gazin/datasets/master/ageSexDistribution.csv") 
population <- population %>% 
    tidyr::gather(sex, number, -year, - ageGroup) %>% 
    mutate(ageGroup = factor(ageGroup, 
          ordered = TRUE, 
          levels = c("0-4", "5-9", "10-14", "15-19", "20-24", 
             "25-29", "30-34", "35-39", "40-44", 
             "45-49", "50-54", "55-59", "60-64", 
             "65-69", "70-74", "75-79", "80-84", 
             "85-89", "90-94", "95-99", "100+")), 
     ageGroup = `[<-`(ageGroup,is.na(ageGroup),value="100+"), 
     number = number/10^6) %>% 
    dplyr::filter(year == 1990) %>% 
    select(-year)