2013-04-24 7 views
15

sto cercando di produrre un piechart sfaccettato con ggplot e affrontare i problemi con l'immissione di testo al centro di ogni fetta:ggplot, sfaccettatura, piechart: collocare il testo nel mezzo del grafico a torta fette

dat = read.table(text = "Channel Volume Cnt 
         AGENT high 8344 
         AGENT medium 5448 
         AGENT low 23823 
         KIOSK high 19275 
         KIOSK medium 13554 
         KIOSK low 38293", header=TRUE) 

vis = ggplot(data=dat, aes(x=factor(1), y=Cnt, fill=Volume)) + 
    geom_bar(stat="identity", position="fill") + 
    coord_polar(theta="y") + 
    facet_grid(Channel~.) + 
    geom_text(aes(x=factor(1), y=Cnt, label=Cnt, ymax=Cnt), 
      position=position_fill(width=1)) 

l'output: enter image description here

Quali parametri di geom_text deve essere regolato in modo da mettere etichette numeriche nel mezzo di fette Piechart?

La domanda correlata è Pie plot getting its text on top of each other ma non gestisce il caso con sfaccettatura.

UPDATE: seguendo i consigli di Paul Hiemstra e l'approccio nella domanda di cui sopra ho cambiato codice come segue:

---> pie_text = dat$Cnt/2 + c(0,cumsum(dat$Cnt)[-length(dat$Cnt)]) 

    vis = ggplot(data=dat, aes(x=factor(1), y=Cnt, fill=Volume)) + 
    geom_bar(stat="identity", position="fill") + 
    coord_polar(theta="y") + 
    facet_grid(Channel~.) + 
    geom_text(aes(x=factor(1), 
--->    y=pie_text, 
        label=Cnt, ymax=Cnt), position=position_fill(width=1)) 

Come mi aspettavo tweaking coordiantes testo è assoluta, ma ha bisogno di essere all'interno di dati sfaccettatura: enter image description here

+3

mia ultima soluzione a questo problema è di evitare i grafici a torta, quando possibile :-) – topchef

risposta

25

nuova risposta: Con l'introduzione di ggplot2 v2.2.0, position_stack() può essere utilizzato per posizionare le etichette, senza la necessità di calcolare una variabile prima posizione.Il seguente codice vi darà lo stesso risultato come il vecchio risposta:

ggplot(data = dat, aes(x = "", y = Cnt, fill = Volume)) + 
    geom_bar(stat = "identity") + 
    geom_text(aes(label = Cnt), position = position_stack(vjust = 0.5)) + 
    coord_polar(theta = "y") + 
    facet_grid(Channel ~ ., scales = "free") 

Per rimuovere centro "vuoto", adattare il codice a:

ggplot(data = dat, aes(x = 0, y = Cnt, fill = Volume)) + 
    geom_bar(stat = "identity") + 
    geom_text(aes(label = Cnt), position = position_stack(vjust = 0.5)) + 
    scale_x_continuous(expand = c(0,0)) + 
    coord_polar(theta = "y") + 
    facet_grid(Channel ~ ., scales = "free") 

RISPOSTA VECCHIO: La soluzione a questo problema sta creando una variabile di posizione, che può essere eseguita abbastanza facilmente con la base R o con i pacchetti data.table, plyr o dplyr:

Fase 1: Creare la variabile di posizione per ciascun canale

# with base R 
dat$pos <- with(dat, ave(Cnt, Channel, FUN = function(x) cumsum(x) - 0.5*x)) 

# with the data.table package 
library(data.table) 
setDT(dat) 
dat <- dat[, pos:=cumsum(Cnt)-0.5*Cnt, by="Channel"] 

# with the plyr package 
library(plyr) 
dat <- ddply(dat, .(Channel), transform, pos=cumsum(Cnt)-0.5*Cnt) 

# with the dplyr package 
library(dplyr) 
dat <- dat %>% group_by(Channel) %>% mutate(pos=cumsum(Cnt)-0.5*Cnt) 

Fase 2: Creare la trama sfaccettato

library(ggplot2) 
ggplot(data = dat) + 
    geom_bar(aes(x = "", y = Cnt, fill = Volume), stat = "identity") + 
    geom_text(aes(x = "", y = pos, label = Cnt)) + 
    coord_polar(theta = "y") + 
    facet_grid(Channel ~ ., scales = "free") 

Il risultato:

enter image description here

+0

Grazie per l'aiuto aggiornato. C'è un modo per sbarazzarsi del centro "vuoto"? (Il piccolo cerchio bianco al centro) – jesusgarciab

+0

@jesusgarciab forse un po 'in ritardo, ma ho aggiornato la risposta – Jaap

5

Per modificare la posizione del testo dell'etichetta rispetto alla coordinata, è possibile utilizzare gli argomenti e hjust di geom_text. Questo determinerà la posizione di tutte le etichette contemporaneamente, quindi questo potrebbe non essere quello che ti serve.

In alternativa, è possibile modificare le coordinate dell'etichetta. Definire un nuovo data.frame dove si media la coordinata Cnt (label_x[i] = Cnt[i+1] + Cnt[i]) per posizionare l'etichetta al centro di quella particolare torta. Basta passare questo nuovo data.frame a geom_text in sostituzione dell'originale data.frame.

Inoltre, i pasticci presentano alcuni difetti di interpretazione visiva. In generale non li userei, specialmente dove esistono buone alternative, ad es. un grafico a punti:

ggplot(dat, aes(x = Cnt, y = Volume)) + 
    geom_point() + 
    facet_wrap(~ Channel, ncol = 1) 

, ad esempio, da questa trama è ovvio che Cnt è più elevata per Kiosk che per agente, queste informazioni si perde nella piechart.

enter image description here

+0

Grazie per la risposta - farò un tentativo. Ma non posso essere d'accordo sul fatto che la trama proposta sia la sostituzione del grafico a torta. Il fatto che Cnt sia superiore si perde principalmente a causa dell'etichetta di testo non allineata, secondo me. – topchef

+0

Il grafico a torta non può mostrare la differenza di grandezza tra AGENT e KIOSK perché mostra solo le dimensioni relative. Il testo è molto più difficile da interpretare secondo me. Questo effetto diventa più grande quando si confrontano, diciamo, 5 categorie. Libri come http://www.amazon.com/Elements-Graphing-Data-William-Cleveland/dp/0963488414 sostengono l'uso di grafici come la trama dei punti a favore del diagramma. Basta google per il "grafico a torta del male". –

+0

Sfortunatamente questa risposta non affronta il problema di avere faccette in quanto le lunghezze hanno bisogno di allineamento attraverso la variabile facet. – topchef

0

seguente risposta è parziale, goffo e io non lo accetterà. La speranza è che solleciterà una soluzione migliore.

text_KIOSK = dat$Cnt 
text_AGENT = dat$Cnt 
text_KIOSK[dat$Channel=='AGENT'] = 0 
text_AGENT[dat$Channel=='KIOSK'] = 0 
text_KIOSK = text_KIOSK/1.7 + c(0,cumsum(text_KIOSK)[-length(dat$Cnt)]) 
text_AGENT = text_AGENT/1.7 + c(0,cumsum(text_AGENT)[-length(dat$Cnt)]) 
text_KIOSK[dat$Channel=='AGENT'] = 0 
text_AGENT[dat$Channel=='KIOSK'] = 0 
pie_text = text_KIOSK + text_AGENT 


vis = ggplot(data=dat, aes(x=factor(1), y=Cnt, fill=Volume)) + 
    geom_bar(stat="identity", position=position_fill(width=1)) + 
    coord_polar(theta="y") + 
    facet_grid(Channel~.) + 
    geom_text(aes(y=pie_text, label=format(Cnt,format="d",big.mark=','), ymax=Inf), position=position_fill(width=1)) 

Produce seguente tabella: enter image description here

Come avrete notato che non posso muovere le etichette per il verde (basso).

+0

Hai visto la mia risposta? Penso che dia la soluzione che stavi chiedendo. – Jaap

+1

Sì, grazie. Rimarrò fedele alla regola "no chart" quando possibile, ma per le eccezioni la tua risposta è la risposta :-) – topchef

+0

Hai assolutamente ragione di evitare il grafico a torta il più possibile, ma alcune persone sembrano amarle davvero. Il più delle volte un grafico a barre ben bilanciato è molto più chiaro. – Jaap

4

Vorrei parlare apertamente del modo convenzionale di preparare le torte in ggplot2, ovvero disegnare un barilotto impilato in coordinate polari. Mentre apprezzo l'eleganza matematica di quell'approccio, causa ogni sorta di mal di testa quando la trama non sembra proprio come dovrebbe. In particolare, la regolazione precisa della dimensione della torta può essere difficile. (Se non sai cosa intendo, prova a creare un grafico a torta che si estende fino al bordo del pannello.)

Preferisco disegnare le torte in un normale sistema di coordinate cartesiane, utilizzando geom_arc_bar() da ggforce . Richiede un po 'di lavoro extra sul front end, perché dobbiamo calcolare noi stessi gli angoli, ma è facile e il livello di controllo che otteniamo è più che utile. Ho usato questo approccio nelle risposte precedenti here e here.

I dati (dalla domanda):

dat = read.table(text = "Channel Volume Cnt 
AGENT high 8344 
AGENT medium 5448 
AGENT low 23823 
KIOSK high 19275 
KIOSK medium 13554 
KIOSK low 38293", header=TRUE) 

Il codice pie-disegno:

library(ggplot2) 
library(ggforce) 
library(dplyr) 

# calculate the start and end angles for each pie 
dat_pies <- left_join(dat, 
         dat %>% 
         group_by(Channel) %>% 
         summarize(Cnt_total = sum(Cnt))) %>% 
    group_by(Channel) %>% 
    mutate(end_angle = 2*pi*cumsum(Cnt)/Cnt_total,  # ending angle for each pie slice 
     start_angle = lag(end_angle, default = 0), # starting angle for each pie slice 
     mid_angle = 0.5*(start_angle + end_angle)) # middle of each pie slice, for the text label 

rpie = 1 # pie radius 
rlabel = 0.6 * rpie # radius of the labels; a number slightly larger than 0.5 seems to work better, 
        # but 0.5 would place it exactly in the middle as the question asks for. 

# draw the pies 
ggplot(dat_pies) + 
    geom_arc_bar(aes(x0 = 0, y0 = 0, r0 = 0, r = rpie, 
        start = start_angle, end = end_angle, fill = Volume)) + 
    geom_text(aes(x = rlabel*sin(mid_angle), y = rlabel*cos(mid_angle), label = Cnt), 
      hjust = 0.5, vjust = 0.5) + 
    coord_fixed() + 
    scale_x_continuous(limits = c(-1, 1), name = "", breaks = NULL, labels = NULL) + 
    scale_y_continuous(limits = c(-1, 1), name = "", breaks = NULL, labels = NULL) + 
    facet_grid(Channel~.) 

enter image description here

Per mostrare perché penso che questo approccio sia molto più potente del convenzionale (coord_polar()) approccio, diciamo che vogliamo le etichette all'esterno della torta piuttosto che dentro. Questo crea un paio di problemi, come dovremo regolare hjust e vjust a seconda del lato della torta, un'etichetta cade, e inoltre dovremo rendere il pannello di trama più largo che alto per fare spazio alle etichette sul lato senza generare spazio eccessivo sopra e sotto.La soluzione di questi problemi nell'approccio coordinate polari non è divertente, ma è banale nelle coordinate cartesiane:

# generate hjust and vjust settings depending on the quadrant into which each 
# label falls 
dat_pies <- mutate(dat_pies, 
        hjust = ifelse(mid_angle>pi, 1, 0), 
        vjust = ifelse(mid_angle<pi/2 | mid_angle>3*pi/2, 0, 1)) 

rlabel = 1.05 * rpie # now we place labels outside of the pies 

ggplot(dat_pies) + 
    geom_arc_bar(aes(x0 = 0, y0 = 0, r0 = 0, r = rpie, 
        start = start_angle, end = end_angle, fill = Volume)) + 
    geom_text(aes(x = rlabel*sin(mid_angle), y = rlabel*cos(mid_angle), label = Cnt, 
       hjust = hjust, vjust = vjust)) + 
    coord_fixed() + 
    scale_x_continuous(limits = c(-1.5, 1.4), name = "", breaks = NULL, labels = NULL) + 
    scale_y_continuous(limits = c(-1, 1), name = "", breaks = NULL, labels = NULL) + 
    facet_grid(Channel~.) 

enter image description here