Plot odds ratio by groups with ggplot for many variables - r

I am making a plot for 17 symptoms by age group. So far I got what I want when I use my code for just one symptom ( code and plot below), but when running the code through all the variables, I am getting a worng plot and still have no idea where I got it wrong.
This is my data:
x <- data.frame(symptoms=c("symptom1: 0 to 9","symptom1: 10 to 19","symptom1: 20 to 49","symptom1: 50+","symptom2: 0 to 9","symptom2: : 10 to 19",
"symptom2: : 20 to 49","symptom2: 50+","symptom3: 0 to 9","symptom3: 10 to 19","symptom3: 20 to 49","symptom3: 50+",
"symptom4: 0 to 9",
"symptom4: 10 to 19","symptom4: 20 to 49","symptom4:50+","symptom5: 0 to 9","symptom5: 10 to 19","symptom5: 20 to 49",
"symptom5: 50+",
"symptom6: 0 to 9","symptom6: 10 to 19","symptom6: 20 to 49","symptom6: 50+","symptom7: 0 to 9","symptom7: 10 to 19","symptom7: 20 to 49",
"symptom7: 50+", "symptom8: 0 to 9","symptom8: 10 to 19","symptom8: 20 to 49","symptom8: 50+",
"symptom9: 0 to 9","symptom9: 10 to 19","symptom9: 20 to 49","symptom9: 50+","symptom10: 0 to 9","symptom10: 10 to 19",
"symptom10: 20 to 49","symptom10: 50+","symptom11: 0 to 9","symptom11: 10 to 19","symptom11: 20 to 49",
"symptom11: 50+","symptom12: 0 to 9","symptom12: 10 to 19","symptom12: 20 to 49","symptom12: 50+","symptom13: 0 to 9",
"symptom13: 10 to 19","symptom13: 20 to 49","symptom13: 50+","symptom14: 0 to 9","symptom14: 10 to 19",
"symptom14: 20 to 49","symptom14: 50+","symptom15: 0 to 9","symptom15: 10 to 19","symptom15: 20 to 49","symptom15: 50+",
"symptom16:0 to 9","symptom16:10 to 19","symptom16:20 to 49","symptom16:50+","symptom17: 0 to 9","symptom17: 10 to 19",
"symptom17: 20 to 49","symptom17: 50+"),
OR=c(3.1,3,0.6,0.2,2,2.5,5,1.8,7.4,4.2,6.9,2.3,3.7,2.7,3.7,5.1,6.8,3.4,4.4,8.3,14540102.8,1036435.3,8070307.6,565044.8,2.9,1.7,2.6,4.2,3.4,1.3,2.5,2.9,1,1.6,48.4,2.6,1.3,1.9,2.6,4.5,0.8,0.7,3.6,0,7.5,14.8,2.7,3.8,1.5,3.2,3.1,0.8,2.4,12,4.5,1.7,2.8,1.8,3.1,1.9,3.3,25,5,1.4,430072.7,5.8,2.8,1.5),
Lower=c(1.3,1.6,0.2,0,1.6,1.7,1.6,0.7,2.2,1.3,2.6,0.3,1.9,1.8,1.4,2,3.3,2.2,2.2,3.2,0,0,0,0,1.5,1.2,1.3,1.5,1.8,0.9,1.3,1.2,0.3,0.6,1.3,0.4,0.9,1.2,1.3,1.7,0.2,0.3,0.4,NA,3.8,8,1.4,1.5,0.7,1.6,1.3,0.3,1.2,9.1,2.2,0.7,0.7,0.6,1.1,0.3,1.3,9,1.5,0.4,0,2.5,0.9,0.1),
Upper=c(8.7,6.3,2.2,4.2,6.1,3.8,8,4.7,26,7.9,19,14.7,7.6,4,6,15.1,14.1,5.3,8.8,22.8,NA,5.463E+98,NA,NA,5.5,4.6,5.2,15.5,6.6,2,5,7.5,3.2,4.2,165.4,22.4,3.5,2.8,5,12.3,2.6,1.6,76.8,2.0619295829016E+205,15.1,30.1,5.4,10.2,3,6.7,9.4,2.1,4.6,28,9.7,7.3,9.9,4.8,8,4.7,11,46.4,23.1,5.6,NA,16,9.1,38.8),
group=rep(c("0-9 years", "10-19 years", "20-49 years", "50+ years"), 17))
This is the code for just the first symptom:
ggplot(x[1:4,] , aes(x = OR, y = 4:1, group=group)) +
geom_vline(aes(xintercept = 1), size = .25, linetype = "dashed") +
geom_errorbarh(aes(xmax = Upper, xmin = Lower), size = 1, height = .1, color = "blue") +
geom_point(aes(shape=group, color=group), size = 5) +
scale_shape_manual(values=c(15,15,15,15)) +
scale_color_manual(values=c('red','green', 'orange', "grey")) +
theme_bw() +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank()) +
scale_y_continuous(breaks = 4:1, labels = x$symptoms[1:4]) +
scale_x_continuous(breaks = seq(0,20,1) ) +
ylab("") +
xlab("Odds ratio") +
ggtitle("Odd ratios (OR) with 95% COnfidence Interval")
and this is the plot that I got with just the first symptom by age group:
When I repeat this for all symptoms so I can have everything in one plot, the plot is a mess. See below code:
ggplot(x , aes(x = OR, y = 68:1, group=group)) +
geom_vline(aes(xintercept = 1), size = .25, linetype = "dashed") +
geom_errorbarh(aes(xmax = Upper, xmin = Lower), size = 1, height = .1, color = "blue") +
geom_point(aes(shape=group, color=group), size = 5) +
scale_shape_manual(values=c(15,15,15,15)) +
scale_color_manual(values=c('red','green', 'orange', "grey")) +
theme_bw() +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank()) +
scale_y_continuous(breaks = 68:1, labels = x$symptoms) +
scale_x_continuous(breaks = seq(0,20,1) ) +
ylab("") +
xlab("Odds ratio") +
ggtitle("Odd ratios (OR) with 95% Confidence Interval")
This is the ugly plot:
At the end, I should have something like the figure below, with the frequency tables at the left and values for OR with 95%CI. I haven't try that one yet (to add all the numbers etc), but suggestions are more than welcome.
Thanks a lot for helping me to debug my code

This is a forest plot. Your main problem is that a couple of your values are several orders of magnitude greater than the rest. Typically with a forest plot, you want a log scale for the odds ratio to make it symmetrical around one. However, even that won't be enough here to resolve the details on your plot, so I have simply filtered out the outliers (which appear nonsensical)
Since you effectively have nested factor levels, I have "silently" faceted the plot.
library(dplyr)
x %>%
mutate(Upper = replace(Upper, abs(Upper) > 100, NA),
Lower = replace(Lower, abs(Lower) > 100, NA),
OR = replace(OR, abs(OR) > 100, NA),
symptoms = factor(gsub(":.*$", "", symptoms),
levels = paste0("symptom", 1:17))) %>%
ggplot(aes(x = OR, y = group)) +
geom_rect(aes(xmin = 0.001, xmax = 1000,
ymin = -Inf, ymax = Inf, fill = symptoms)) +
geom_errorbarh(aes(xmin = Lower, xmax = Upper)) +
geom_point(aes(colour = group, shape = group), size = 5 ) +
geom_vline(aes(xintercept = 1), linetype = 2) +
scale_shape_manual(values = rep(15, 5)) +
scale_fill_manual(values = rep(c("#ffffff00", "#f0f0f090"), 9)[-1],
guide = "none") +
scale_x_log10() +
coord_cartesian(xlim = c(0.01, 100)) +
facet_grid(symptoms~., switch = "y") +
theme_bw() +
theme(panel.spacing.y = unit(0, "points"),
panel.border = element_blank(),
axis.text.y = element_blank(),
axis.ticks.length.y = unit(0, "points"),
strip.text.y.left = element_text(angle = 0),
strip.background.y = element_blank(),
strip.placement = "outside",
axis.line = element_line()
)
You may also wish to check out the ggforest package.

Related

How to customize Horizontal dots plot?

I want to plot customized Horizontal dots using my data and the code given here
data:
df <- data.frame (origin = c("A","B","C","D","E","F","G","H","I","J"),
Percentage = c(23,16,32,71,3,60,15,21,44,60),
rate = c(10,12,20,200,-25,12,13,90,-105,23),
change = c(10,12,-5,12,6,8,0.5,-2,5,-2))
.
origin Percentage rate change
1 A 23 10 10.0
2 B 16 12 12.0
3 C 32 20 -5.0
4 D 71 200 12.0
5 E 3 -25 6.0
6 F 60 12 8.0
7 G 15 13 0.5
8 H 21 90 -2.0
9 I 44 -105 5.0
10 J 60 23 -2.0
obs from 'origin' column need be put on y-axis. corresponding values in 'change' and 'rate' column must be presented/differentiated through in box instead of circles, for example values from 'change' column in lightblue and values from 'rate' column in blue. In addition I want to add second vertical axis on right and put circles on it which size will be defined based on corresponding value in 'Percentage' column.
Output of code from the link:
Expected outcome (smth. like this:
Try this.
First, reshaping so that both rate and change are in one column better supports ggplot's general preference towards "long" data.
df2 <- reshape2::melt(df, id.vars = c("origin", "Percentage"))
(That can also be done using pivot_wider.)
The plot:
ggplot(df2, aes(value, origin)) +
geom_label(aes(label = value, fill = variable, color = variable)) +
geom_point(aes(size = Percentage), x = max(df2$value) +
20, shape = 21) +
scale_x_continuous(expand = expansion(add = c(15, 25))) +
scale_fill_manual(values = c(change="lightblue", rate="blue")) +
scale_color_manual(values = c(change="black", rate="white")) +
theme_bw() +
theme(panel.border = element_blank(), panel.grid.major.x = element_blank(), panel.grid.minor.x = element_blank()) +
labs(x = NULL, y = NULL)
The legend and labels can be adjusted in the usual ggplot methods. Overlapping of labels is an issue with which you will need to contend.
Update on OP request: See comments:
gg_dot +
geom_text(aes(x = rate, y = origin,
label = paste0(round(rate, 1), "%")),
col = "black") +
geom_text(aes(x = change, y = origin,
label = paste0(round(change, 1), "%")),
col = "white") +
geom_text(aes(x = x, y = y, label = label, col = label),
data.frame(x = c(40 - 1.1, 180 + 0.6), y = 11,
label = c("change", "rate")), size = 6) +
scale_color_manual(values = c("#9DBEBB", "#468189"), guide = "none") +
scale_y_discrete(expand = c(0.2, 0))
First answer:
Something like this?
library(tidyverse)
library(dslabs)
gg_dot <- df %>%
arrange(rate) %>%
mutate(origin = fct_inorder(origin)) %>%
ggplot() +
# remove axes and superfluous grids
theme_classic() +
theme(axis.title = element_blank(),
axis.ticks.y = element_blank(),
axis.line = element_blank()) +
# add a dummy point for scaling purposes
geom_point(aes(x = 12, y = origin),
size = 0, col = "white") +
# add the horizontal discipline lines
geom_hline(yintercept = 1:10, col = "grey80") +
# add a point for each male success rate
geom_point(aes(x = rate, y = origin),
size = 11, col = "#9DBEBB") +
# add a point for each female success rate
geom_point(aes(x = change, y = origin),
size = 11, col = "#468189")
gg_dot +
geom_text(aes(x = rate, y = origin,
label = paste0(round(rate, 1))),
col = "black") +
geom_text(aes(x = change, y = origin,
label = paste0(round(change, 1))),
col = "white") +
geom_text(aes(x = x, y = y, label = label, col = label),
data.frame(x = c(40 - 1.1, 180 + 0.6), y = 11,
label = c("change", "rate")), size = 6) +
scale_color_manual(values = c("#9DBEBB", "#468189"), guide = "none") +
scale_y_discrete(expand = c(0.2, 0))

Overlapping labels with multiple geom_text statements in ggplot2

I'm making a ggplot with error bars and there is overlapping text on one of them even though I'm using check_overlap = T in the geom_text function
It's mostly an issue with the last hospital
ggplot(scores, aes(x=hospital, y=mean), xlab = "score") +
geom_point(shape = 19, size = 3) +
#geom_line(color="blue") +
geom_errorbar(aes(x=hospital, ymin=lower, ymax=upper), color="blue", width=.5) +
coord_flip() +
theme(axis.text=element_text(size=20), axis.title=element_text(size=25,face="bold"),
plot.title = element_text(face = "bold", size = 30)) +
ggtitle("Linear Regression Predictions for Scores") + ylab("Score")+ xlab("Hospital") +
labs(subtitle = "Data used for Prediction: 2014-2019") +
geom_text(aes(label = round(mean, 1)),vjust = -1, size = 8, check_overlap = T)+
geom_text(aes(y = lower, label = round(lower, 1)),vjust= -1, size = 8, check_overlap = T) +
geom_text(aes(y = upper, label = round(upper, 1)),vjust = -1, size = 8, check_overlap = T)
Dataframe:
hospital mean lower upper
1 A 50 40 60
2 B 60 55 65
3 C 80 78 82
4 D 70 65 75
5 E 99 98 100
I suggest using a long version of the data for the text so you can get it in one call to geom_text:
library(dplyr); library(tidyr); library(ggplot2)
scores_long <- hospital %>%
gather(stat, value, -hospital) %>%
mutate(value = round(value, 1))
ggplot(data = scores, aes(x=hospital, y=mean), xlab = "score") +
geom_errorbar(aes(x=hospital, ymin=lower, ymax=upper), color="blue", width=.5) +
geom_point(shape = 19, size = 3) +
coord_flip() +
theme(axis.text=element_text(size=20), axis.title=element_text(size=25,face="bold"),
plot.title = element_text(face = "bold", size = 30)) +
ggtitle("Linear Regression Predictions for Scores") + ylab("Score")+ xlab("Hospital") +
labs(subtitle = "Data used for Prediction: 2014-2019") +
geom_text(data = scores_long,
aes(label = value, y = value), vjust = -1, size = 8, check_overlap = T)

How to add more lines to a plot done with ggplot2 that has double y-axis

The dataframe df1 summarizes the mean daily depth (meanDepth) of a fish throught time, also the mean daily water temperature to different depths (T5m, T15m, T25m and T35m) and the overall mean daily temperature (meanT) for the whole water column (without considering different depths). As an example:
df1<- data.frame(Date=c("2016-08-05","2016-08-06","2016-08-07","2016-08-08","2016-08-09","2016-08-10"),
meanDepth=c(15,22,18,25,27,21),
T5m=c(17,18,21,23,21,18),
T15m=c(16,17,18,19,18,17),
T25m=c(16,17,17,18,18,17),
T35m=c(15,16,17,17,17,16),
meanT=c(16,17.2,17.8,18.3,17.8,17.4))
df1$Date<-as.Date(df1$Date)
df1
Date meanDepth T5m T15m T25m T35m meanT
1 2016-08-05 15 17 16 16 15 16.0
2 2016-08-06 22 18 17 17 16 17.2
3 2016-08-07 18 21 18 17 17 17.8
4 2016-08-08 25 23 19 18 17 18.3
5 2016-08-09 27 21 18 18 17 17.8
6 2016-08-10 21 18 17 17 16 17.4
I want to plot in one graph both the depth profile of the fish and the mean daily temperature for the different depths.
What I've got so far is to plot in one Y-axis the meanDepth and in the other y-axis the meanT. But I don't know how to add more lines related to the right-y-axis (=Temperature) that represent the mean daily temperature for different depths. Here you have the code I've been able to built so far.
p <- ggplot(df1, aes(x = Date))
p <- p + geom_line(aes(y = meanDepth, colour = "Overall daily mean depth"))
p <- p + geom_line(aes(y = meanT/(max(range(df1$meanT,na.rm=TRUE)/max(range(df1$meanDepth,na.rm=TRUE)))), colour = "Mean Water T"))
p <- p + scale_y_continuous(sec.axis = sec_axis(~.*(max(range(df1$meanT,na.rm=TRUE)/max(range(df1$meanDepth,na.rm=TRUE)))), name = "Mean daily Temp"))
p <- p + scale_colour_manual(values = c("blue", "red"))
p <- p + labs(title="Mean daily depth and water temperature through time",
y = "Mean daily depth",
x = "Date",
colour = "Parameter")
p <- p + theme(legend.position = c(0.8, 0.9), plot.title = element_text(hjust=0.5, face="bold",margin = margin(0,0,12,0) ),axis.title.y =element_text(margin = margin(t = 0, r = 12, b = 0, l = 0)),axis.title.x =element_text(margin = margin(t = 12, r = 0, b = 0, l = 0)),axis.text.x=element_text(angle=60, hjust=1))
p <- p + scale_x_date(date_breaks = "1 days", labels = date_format("%Y-%m-%d"))
p
That's the plot I've got:
Does anyone how to add the lines referred to the temperatures at 5, 15, 25 and 35 meters?
Secondary axes are almost never a good idea, but you could do something like this:
library(ggplot2)
library(tidyr)
library(dplyr)
df1 %>%
mutate(meanDepthNormalized = meanDepth * max(meanT) / max(meanDepth)) %>% #1
select(-meanDepth) %>%
# could have changed meanDepth before directly, but wanted to be more verbose
gather(type, value, -Date) %>% #2
ggplot(aes(x = Date, y = value, color = type, linetype = type == "meanT")) + #3
geom_line(size = 1.5) +
scale_y_continuous(sec.axis = sec_axis(~ . * max(df1$meanDepth) / max(df1$meanT))) +
scale_color_manual("", values = c("#E41A1C", "#984EA3", "#BDD7E7",
"#6BAED6", "#3182BD", "#08519C")) +
theme_minimal() +
guides(linetype = FALSE) +
theme(legend.position = "top")
Explanation
You first transform your Depth to be on the same scale as the temperature measurements
Then you transform your data from wide to long format via gather
Then you can map color to the type variable which holds basically the former column names.
If I understood your question correctly, it's sufficient to add a geom_line for each of the temperatures/columns. Here's an example with T5m and T35m.
df1<- data.frame(Date=c("2016-08-05","2016-08-06","2016-08-07",
"2016-08-08","2016-08-09","2016-08-10"),
meanDepth=c(15,22,18,25,27,21),
T5m=c(17,18,21,23,21,18),
T15m=c(16,17,18,19,18,17),
T25m=c(16,17,17,18,18,17),
T35m=c(15,16,17,17,17,16),
meanT=c(16,17.2,17.8,18.3,17.8,17.4))
df1$Date<-as.Date(df1$Date)
norm <- max(df1$meanT,na.rm=TRUE)/max(df1$meanDepth,na.rm=TRUE)
p <- ggplot(df1, aes(x = Date)) +
geom_line(aes(y = meanDepth, colour = "Overall daily mean depth")) +
geom_line(aes(y = meanT/norm, colour = "Mean Water T")) +
geom_line(aes(y = T5m/norm, colour = "T5m")) +
geom_line(aes(y = T35m/norm, colour = "T35m")) +
scale_x_date(date_breaks = "1 days", labels = date_format("%Y-%m-%d")) +
scale_y_continuous(sec.axis = sec_axis(~.*norm, name = "Mean daily Temp")) +
scale_colour_manual(values = c("blue", "red", "orange", "black")) +
labs(title="Mean daily depth and water temperature through time",
y = "Mean daily depth",
x = "Date",
colour = "Parameter") +
theme(legend.position = c(0.8, 0.9),
plot.title = element_text(hjust=0.5, face="bold", margin = margin(0,0,12,0)),
axis.title.y = element_text(margin = margin(t = 0, r = 12, b = 0, l = 0)),
axis.title.x = element_text(margin = margin(t = 12, r = 0, b = 0, l = 0)),
axis.text.x=element_text(angle=60, hjust=1))
p
maybe with facet_grid(y ~., scales = "free")
here a example
ggplot(mtcars, aes(factor(cyl), mpg)) +
geom_boxplot() +
facet_grid(cyl ~., scales = "free")

Add vertical offset to stacked column plot with ggplot2

I have several stacked column charts representing drilling profiles. I want to offset the y-position of each Borehole to represent the actual height on the ground.
My Data looks like this:
x layer.thickness layer.depth Petrography BSCategory Offset
0 0.2 0.2 silt Drilling1 0
0 1.0 1.2 gravel Drilling1 0
0 3.0 4.2 silt Drilling1 0
4 0.4 0.4 silt Drilling2 -1
4 0.8 1.2 gravel Drilling2 -1
4 2.0 3.2 sand Drilling2 -1
My minimum working code so far is this:
df <- data.frame(x=c(0,0,0,4,4,4), layer.thickness = c(0.2,1.0,3.0,0.4,0.8,2.0),
layer.depth = c(0.2,1.2,4.2,0.4,1.2,3.2),
Petrography = c("silt", "gravel", "silt", "silt", "gravel", "sand"),
BSCategory = c("Drilling1","Drilling1","Drilling1","Drilling2","Drilling2","Drilling2"),
Offset = c(0,0,0,-1,-1,-1))
# provide a numeric ID that stops grouping individual petrography items
df <- transform(df,ix=as.numeric(factor(df$BSCategory)));
drilling <- ggplot(data = df, aes(x = x, y = layer.thickness, group = ix, fill = Petrography)) +
theme_minimal() +
theme(axis.line = element_line(colour = "black"),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_blank(),
panel.background = element_blank(),
axis.line.x = element_blank(),
axis.ticks.y = element_line(),
aspect.ratio=1) +
geom_col(position = position_stack(reverse = TRUE), width= .15,color="black") +
scale_y_reverse(expand = c(0, 0), name ="Depth [m]") +
scale_x_continuous(position = "top", breaks = df$x, labels=paste(df$BSCategory,"\n",df$x,"m"), name="") +
scale_fill_manual(values = c("gravel"='#f3e03a', "sand"= '#e09637', "silt"='#aba77d'))
print(drilling)
This is my output so far (with red indicating what it should look like):
It may be easier to work with geom_rect here (bar charts are/should be anchored at zero). First we need to calculate y start and end positions for each sample:
library(data.table)
setDT(df)[ , `:=`(start = start <- c(Offset[1] + c(0, cumsum(head(layer.thickness, -1)))),
end = start + layer.thickness), by = BSCategory]
geom_rect has both fill and color aesthetics, which makes it easy to add a border to each individual sample.
w <- 0.5 # adjust to desired width
ggplot(data = df, aes(xmin = x - w / 2, xmax = x + w / 2, ymin = start, ymax = end,
fill = Petrography, group = Petrography)) +
geom_rect(color = "black") +
scale_y_reverse(expand = c(0, 0), name ="Depth [m]") +
scale_x_continuous(position = "top", breaks = df$x, labels = paste(df$BSCategory,"\n", df$x, "m"), name = "") +
scale_fill_manual(values = c("gravel" = '#f3e03a', "sand" = '#e09637', "silt" = '#aba77d')) +
theme_classic() +
theme(axis.line.x = element_blank(),
axis.ticks.x = element_blank())
Alternatively, geom_segment could be used. geom_segment doesn't have a fill aes, so we need to change to color:
ggplot(data = df, aes(x = x, xend = x, y = start, yend = end, color = Petrography, group = ix)) +
geom_segment(size = 5) +
scale_y_reverse(expand = c(0, 0), name ="Depth [m]") +
scale_x_continuous(position = "top", breaks = df$x, labels = paste(df$BSCategory,"\n", df$x, "m"), name = "") +
scale_color_manual(values = c("gravel" = '#f3e03a', "sand" = '#e09637', "silt" = '#aba77d')) +
theme_classic() +
theme(axis.line.x = element_blank(),
axis.ticks.x = element_blank())
To add borders, see Add border to segments in geom_segment.

Plot frequencies on a polar plot using angle data, ggplot2

I am trying to use a polar plot to represent frequencies according to a circular angle/direction (0-360 degrees). For some reason I am having problems trying to define the scale in the plot to represent all 3 angles. At the moment only 2 are showing ("B" and "C"). Any help will be appreciated. Thanks in advance,
library(ggplot2)
data <- read.table(text = "stat angle freq perc
A 1 720 79
B 223.5017 121 13
C 117.9372 68 7", header=T)
head(data)
str(data)
db<-data
db$stat<-factor(db$stat)
levels(db$stat)
# Plot
bp<-ggplot(db, aes(x = angle, y = perc), fill = factor(stat)) +
geom_bar(stat="identity", colour="grey100", aes(fill = factor(stat),
width = 16)) +
coord_polar(theta="x", start=0) +
theme_minimal() + ylab("Detections (%)") +
scale_x_continuous("", lim=c(0,360), breaks = seq(0, 315, 45),
labels = c("N","NE","E","SE","S","SW","W","NW"))
bp2<-bp + theme(panel.grid.major = element_line(colour = "grey60", size=0.45),
panel.grid.minor = element_line(colour = "grey60", size=0.45))
Width in geom_bar is the issue. Following works:
ggplot(db) +
geom_bar(stat="identity",
colour="grey100",
aes(x = angle, y = perc, fill = stat, width = 2)) +
coord_polar() +
theme_minimal() +
ylab("Detections (%)")+
scale_x_continuous(limits=c(0,360),
breaks = seq(0, 315, 45),
labels = c("N","NE","E","SE","S","SW","W","NW"))

Resources