fancy pie chart in R using ggplot2 - r

I have a pie chart below, and I would like to leave extra white space between each pie and paste the value of each letter in each pie (A=25). how to get around this? many thanks in advance.
library(ggplot2)
library(dplyr)
# Create Data
data <- data.frame(
group=LETTERS[1:5],
value=c(13,7,9,21,2)
)
# Compute the position of labels
data <- data %>%
arrange(desc(group)) %>%
mutate(prop = value / sum(data$value) *100) %>%
mutate(ypos = cumsum(prop)- 0.5*prop )
# Basic piechart
ggplot(data, aes(x="", y=prop, fill=group)) +
geom_bar(stat="identity", width=10, color="white") +
coord_polar("y", start=0) +
theme_void() +
theme(legend.position="none") +
geom_text(aes(y = ypos, label = round(prop,2) ), color = "white", size=4) +
scale_fill_brewer(palette="Set1")

You could do:
ggplot(data, aes(x="", y=prop, fill=group)) +
geom_bar(stat="identity", width=10, size = 3, color = "white") +
coord_polar("y", start=0) +
theme_void() +
theme(legend.position="none") +
geom_text(aes(y = ypos, label = paste(group, round(prop,2), sep = "\n")),
color = "white", size=4, nudge_x = 3) +
scale_fill_brewer(palette="Set1")

Perhaps 3D pie chart in base R can work with explode argument set to true, e.g.
pie3D(num_data, labels = num_data, explode = 0.25)

Related

Plot a wheel (pie) chart in R

I have a pie chart below, and I would like to create the wheel much more readable. many thanks in advance.
library(ggplot2)
library(dplyr)
# Create Data
data <- data.frame(
group=c("IMAGINE HERE I HAVE A LONG SURVEY QUESTIONS",
"AND THE TEXT IS IN DIFFERENT LENGTH",
"WANNA PLOT THESE IN ORDER",
"WITH 45 DEGREE ANGLE",
"PIE CAN BE PROPORTIONAL WITH THE TEXT LENGTH" ),
value=c(13,7,9,21,2)
)
# Compute the position of labels
data <- data %>%
arrange(desc(group)) %>%
mutate(prop = value / sum(data$value) *100) %>%
mutate(ypos = cumsum(prop)- 0.5*prop )
# Basic piechart
ggplot(data, aes(x="", y=prop, fill=group)) +
geom_bar(stat="identity", width=1, color="white") +
coord_polar("y", start=0) +
theme_void() +
theme(legend.position="none") +
geom_text(aes(y = ypos, label = group), color = "white", size=6) +
scale_fill_brewer(palette="Set1")
You have a better chance of fitting in the labels if you wrap them using str_wrap and curve them using geomtextpath:
# Compute the position of labels
data <- data %>%
arrange(desc(group)) %>%
mutate(prop = value / sum(data$value) *100) %>%
mutate(ypos = cumsum(prop)- 0.5*prop,
group = stringr::str_wrap(group, 20))
library(geomtextpath)
# Basic piechart
ggplot(data, aes(x=1, y=prop, fill=group)) +
geom_bar(stat="identity", width=1, color="white") +
coord_polar("y", start=0) +
theme_void() +
theme(legend.position="none") +
geom_textpath(aes(x = 1.3, y = ypos, label = group,
vjust = group), color = "black", size = 6,
angle = 90) +
scale_fill_brewer(palette="Set1") +
scale_x_continuous(limits = c(0.5, 2)) +
scale_vjust_manual(values = c(2, 1.8, 2.2, 2, 4))
Maybe to wrap the group text and have it plotted in many lines of text is a way to solve the problem.
First strwrap breaks each value in group, then paste inserts newline characters at the break points.
And the graphics device dimensions are made larger, though it doesn't show with reprex.
suppressPackageStartupMessages({
library(ggplot2)
library(dplyr)
})
# Create Data
data <- data.frame(
group=c("IMAGINE HERE I HAVE A LONG SURVEY QUESTIONS",
"AND THE TEXT IS IN DIFFERENT LENGTH",
"WANNA PLOT THESE IN ORDER",
"WITH 45 DEGREE ANGLE",
"PIE CAN BE PROPORTIONAL WITH THE TEXT LENGTH" ),
value=c(13,7,9,21,2)
)
# Compute the position of labels
data <- data %>%
arrange(desc(group)) %>%
mutate(prop = value / sum(data$value) *100) %>%
mutate(ypos = cumsum(prop) - 0.5*prop )
data$group2 <- sapply(data$group, \(x) {
s <- strwrap(x, width = 15)
paste(s, collapse = "\n")
})
old_par <- par()[c("fin", "mar")]
par(fin = old_par$fin + 5, mar = rep(0, 4))
# Basic piechart
ggplot(data, aes(x="", y=prop, fill=group)) +
geom_bar(stat="identity", width=1, color="white") +
coord_polar("y", start=0) +
theme_void() +
theme(legend.position="none") +
geom_text(aes(y = ypos, label = group2), color = "white", size=3) +
scale_fill_brewer(palette="Set1")
par(old_par)
Created on 2022-05-29 by the reprex package (v2.0.1)
It is not the best option, but you could use geom_label_repel from ggrepel like this:
library(ggplot2)
library(dplyr)
library(ggrepel)
data <- data %>%
arrange(desc(group)) %>%
mutate(prop = value / sum(data$value) *100) %>%
mutate(ypos = cumsum(prop)- 0.5*prop )
ggplot(data, aes(x="", y=prop, fill=group)) +
geom_bar(stat="identity", width=1, color="white") +
coord_polar("y", start=0) +
theme_void() +
theme(legend.position="none") +
geom_label_repel(data = data,
aes(y = ypos, label = group),
size = 4.5, nudge_x = 3, show.legend = FALSE) +
scale_fill_brewer(palette="Set1")
Output:

Pie chart with ggplot2 using data from read.csv2

I am trying to create a pie chart with ggplot. I want to show how many hours I use for diffrent tasks at work everyday.
# Libraries
library(ggplot2)
library(tidyverse) # function "%>%"
# 1. Read data (semicolon separated)
res = read.csv2(text = "Activity;No_of_Hours
Work;3
Lunch;1
Meetings;2
Talking;1")
# 2. Print table
df <- as.data.frame(res)
df
# 3. Plot Pie chart
res %>% ggplot(aes(x="", # we leave x blank with ""
y= Activity,
fill=No_of_Hours)) +
geom_bar(stat="identity") +
coord_polar("y", start=0) +
theme_classic()
You can calculate the y position of labels with coord_polar and plot them with geom_text
df <- df %>%
arrange(desc(Activity)) %>%
mutate(prop = No_of_Hours / sum(df$No_of_Hours) *100) %>%
mutate(ypos = cumsum(prop)- 0.5*prop )
# 3. Plot Pie chart
ggplot(df, aes(x="", y=prop, fill=Activity)) +
geom_bar(stat="identity", width=1, color="white") +
coord_polar("y", start=0) +
theme_void() +
geom_text(aes(y = ypos, label = No_of_Hours), color = "white", size=6)
which give you:
Or more similar to your example (but IMHO less informative):
ggplot(df, aes(x="", y=prop, fill=No_of_Hours)) +
geom_bar(stat="identity", width=1, color="white") +
coord_polar("y", start=0) +
theme_void() +
geom_text(aes(y = ypos, label =Activity ), color = "white", size=6)
which give you:

using subscript in legend

I created a pie chart like so:
library(dplyr)
library(ggplot2)
#Data
data <- data.frame(my_val = c(10, 12, 4),
my_var = c("A1B", "H2C3+", "LO4"),
stringsAsFactors = F)
#Create variable
data <- data %>%
mutate(per=my_val/sum(my_val)) %>%
arrange(desc(my_var))
data$label <- scales::percent(data$per)
#Plot
ggplot(data=data)+
geom_bar(aes(x="", y=per, fill=my_var), stat="identity", width = 1)+
coord_polar("y", start=0)+
theme_void()+
geom_text(aes(x=1, y = cumsum(per) - per/2, label=label))
I would like to add the legend as a subscript like so:
my_lab <- c(expression(H['2'*'C'*phantom("+")]),
expression(A['1']*B),
expression(LO['4']))
How can I add it in my pie chart code above?
Put my_lab above (before) your plot. Index it for your labels.
my_lab <- c(expression(H['2'*'C'*phantom("+")]),
expression(A['1']*B),
expression(LO['4']))
ggplot(data=data) +
geom_bar(aes(x="", y=per, fill=my_var),
stat="identity", width = 1) +
coord_polar("y", start=0) +
theme_void() +
geom_text(aes(x=1, y = cumsum(per) - per/2,
label=label)) +
scale_fill_discrete(name="Variables",
labels=c(my_lab[1],
my_lab[2],
my_lab[3]))
Making use of a named vector you could do:
Note: For a nice (left) alignment of the legend labels I made use of guide_legend(label.hjust = 0).
library(ggplot2)
ggplot(data=data)+
geom_bar(aes(x="", y=per, fill=my_var), stat="identity", width = 1)+
coord_polar("y", start=0)+
theme_void()+
geom_text(aes(x=1, y = cumsum(per) - per/2, label=label)) +
scale_fill_discrete(labels = c(`H2C3+` = expression(H['2'*'C'*phantom("+")]),
A1B = expression(A['1']*B),
LO4 = expression(LO['4']))) +
guides(fill = guide_legend(label.hjust = 0))

ggplot2 pie chart bad position of labels

Sample data
data <- data.frame(Country = c("Mexico","USA","Canada","Chile"), Per = c(15.5,75.3,5.2,4.0))
I tried set position of labels.
ggplot(data =data) +
geom_bar(aes(x = "", y = Per, fill = Country), stat = "identity", width = 1) +
coord_polar("y", start = 0) +
theme_void()+
geom_text(aes(x = 1.2, y = cumsum(Per), label = Per))
But pie chart actually look like:
You have to sort the data before calculating the cumulative sum. Then, you can optimize label position, e.g. by subtracting half of Per:
library(tidyverse)
data %>%
arrange(-Per) %>%
mutate(Per_cumsum=cumsum(Per)) %>%
ggplot(aes(x=1, y=Per, fill=Country)) +
geom_col() +
geom_text(aes(x=1,y = Per_cumsum-Per/2, label=Per)) +
coord_polar("y", start=0) +
theme_void()
PS: geom_col uses stat_identity by default: it leaves the data as is.
Or simply use position_stack
data %>%
ggplot(aes(x=1, y=Per, fill=Country)) +
geom_col() +
geom_text(aes(label = Per), position = position_stack(vjust = 0.5))+
coord_polar(theta = "y") +
theme_void()
From the help:
# To place text in the middle of each bar in a stacked barplot, you
# need to set the vjust parameter of position_stack()

ggplot2: doughnuts, how to conditional color fill with if_else

Following guides like ggplot Donut chart I am trying to draw small gauges, doughnuts with a label in the middle, with the intention to put them later on on a map.
If the value reaches a certain threshold I would like the fill of the doughnut to change to red. Is it possible to achieve with if_else (it would be most natural but it does not work).
library(tidyverse)
df <- tibble(ID=c("A","B"),value=c(0.7,0.5)) %>% gather(key = cat,value = val,-ID)
ggplot(df, aes(x = val, fill = cat)) + scale_fill_manual(aes,values = c("red", "yellow"))+
geom_bar(position="fill") + coord_polar(start = 0, theta="y")
ymax <- max(df$val)
ymin <- min(df$val)
p2 = ggplot(df, aes(fill=cat, y=0, ymax=1, ymin=val, xmax=4, xmin=3)) +
geom_rect(colour="black",stat = "identity") +
scale_fill_manual(values = if_else (val > 0.5, "red", "black")) +
geom_text( aes(x=0, y=0, label= scales::percent (1-val)), position = position_dodge(0.9))+
coord_polar(theta="y") +
xlim(c(0, 4)) +
theme_void() +
theme(legend.position="none") +
scale_y_reverse() + facet_wrap(facets = "ID")
Scale fill manual values= if else.... this part does not work, the error says: Error in if_else(val > 0.5, "red", "black") : object 'val' not found. Is it my error, or some other solution exists?
I also realize my code is not optimal, initially gather waited for more variables to be included in the plot, but I failed to stack one variable on top of the other. Now one variable should be enough to indicate the percentage of completion. I realise my code is redundant for the purpose. Can you help me out?
A solution for the color problem is to first create a variable in the data and then use that to map the color in the plot:
df <- tibble(ID=c("A","B"),value=c(0.7,0.5)) %>% gather(key = cat,value = val,-ID) %>%
mutate(color = if_else(val > 0.5, "red", "black"))
p2 = ggplot(df, aes(fill=color, y=0, ymax=1, ymin=val, xmax=4, xmin=3)) +
geom_rect(colour="black",stat = "identity") +
scale_fill_manual(values = c(`red` = "red", `black` = "black")) +
geom_text( aes(x=0, y=0, label= scales::percent (1-val)), position = position_dodge(0.9))+
coord_polar(theta="y") +
xlim(c(0, 4)) +
theme_void() +
theme(legend.position="none") +
scale_y_reverse() + facet_wrap(facets = "ID")
The result would be:

Resources