R: How to add indifference curves like these? - r

I have got a graph like below, except without the red and blue indifference curves (level sets). I know I can use contour() but that creates long curves going from edge to edge.
Is there any way I can create such curves? They don't have to follow a function or any data in particular, I just wanna show the general picture.

You can try with contour
sig <- seq(0,0.25,by=.01)
exr <- seq(0,.20,length.out = length(sig))
# define function
Uf=function(sig,ret,ra=1)ret-0.5*(1/ra)*sig^2
u = outer(sig,exr,function(sd,mr)Uf(sd,mr,ra=0.075))
#image(sig,exr,u)
#contour(sig,exr,u)
contour(sig,exr,u, levels =c(0.04666667, 0.07500000, 0.10333333),col=3,drawlabels = F)
v = outer(sig,exr,function(sd,mr)Uf(sd,mr,ra=0.195))
contour(sig,exr,v, levels =c(0.07333333, 0.09500000, 0.11666667),add = T,col=4,drawlabels = F)
abline(a=0.03,b=0.6666667)
Edit
Uf is a classical quadratic preference function that depends on risk, return and risk aversion. See more information for example here.
outer fist make all possible combination of the supplied vectors sig and exr, then takes every pair of values and computes the utility with Uf. Try head(u) or View(u).
contour takes all values to plot with the desired levels (indifference curves).
abline adds a reference line that you actually have in your plot.

A handmade solution requiring some fiddling with the position and the curvature:
line <- data.frame(x = 0, xend = 0.2, y = 0.03, yend = 0.18)
ggplot(line, aes(x, y, xend=xend, yend=yend)) +
geom_segment() +
annotate(
"curve",
x = 0.02 - c(0, 0.005, 0.01),
y = 0.08 + c(0, 0.01, 0.02),
xend = 0.08 - c(0, 0.005, 0.01),
yend = 0.14 + c(0, 0.01, 0.02),
color = "red", curvature=0.76) +
expand_limits(y = 0)

Related

Control Label of Contour Lines in `contour()`

I am using image() and contour() to create a "heatmap" of probabilities - for example:
I was asked to change the labels such that they "do not overlap the lines, and the lines are unbroken." After consulting ?contour(), I tried changing to method = "edge" and method = "simple", but both fail print the labels (although the lines are unbroken), and cant seem to find posts regarding similar issues elsewhere.
Any advice on how to manipulate the labels to appear adjacent to (not on top of) unbroken lines would be much appreciated. I would prefer base R but also would welcome options from more flexible packages or alternative base R functions.
Minimal code to recreate example figure is here:
# Generate Data
Rs <- seq(0.02, 1.0, 0.005)
ks <- 10 ^ seq(-2.3, 0.5, 0.005)
prob <- function(Y,R,k) {
exp(lgamma(k*Y+Y-1) - lgamma(k*Y) - lgamma(Y+1) + (Y-1) * log(R/k) - (k*Y+Y-1) * log(1+R/k))
}
P05 <- matrix(NA, ncol = length(ks), nrow = length(Rs))
for(i in 1:length(Rs)) {
for(j in 1:length(ks)) {
P05[i,j] <- 1 - sum(prob(1:(5 - 1), Rs[i], ks[j]))
}
}
colfunc <- colorRampPalette(c("grey25", "grey90"))
lbreaks <- c(-1e-10, 1e-5, 1e-3, 5e-3, 1e-2, 2e-2, 5e-2, 1e-1, 1.5e-1, 1)
## Create Figure
image(Rs, ks, P05,
log="y", col = rev(colfunc(length(lbreaks)-1)), breaks = lbreaks, zlim = lbreaks,
ylim = c(min(ks), 2), xlim = c(0,1))
contour(Rs, ks, P05, levels = lbreaks, labcex = 1, add = TRUE)
There is an easy(ish) way to do this in ggplot, using the geomtextpath package.
First, convert your matrix to an x, y, z data frame:
df <- expand.grid(Rs = Rs, ks = ks)
df$z <- c(P05)
Now plot a filled contour, and then geom_textcontour. By default the text will break the lines, as in contour, but if you set the vjust above one or below zero the lines will close up as they don't need to break for the text.
I've added a few theme and scale elements to match the aesthetic of the base graphics function. Note the text and line size, color etc remain independently adjustable.
library(geomtextpath)
ggplot(df, aes(Rs, ks, z = z)) +
geom_contour_filled(breaks = lbreaks) +
geom_textcontour(breaks = lbreaks, color = 'black', size = 5,
aes(label = stat(level)), vjust = 1.2) +
scale_y_log10(breaks = c(0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2),
expand = c(0, 0)) +
scale_fill_manual(values = rev(colfunc(9)), guide = 'none') +
scale_x_continuous(expand = c(0, 0)) +
theme_classic(base_size = 16) +
theme(axis.text.y = element_text(angle = 90, hjust = 0.5),
axis.ticks.length.y = unit(3, 'mm'),
plot.margin = margin(20, 20, 20, 20))
The contour function is mostly written in C, and as far as I can see, it doesn't support the kinds of labels you want.
So I think there are two ways to do this, neither of which is very appealing:
Modify the source to the function. You can see the start of the labelling code here. I think you would need to rebuild R to incorporate your changes; it's not easy to move a function from a base package to a contributed package.
Draw the plot with no labels, and add them manually after the fact. You could add them using text(), or produce an output file and use an external program to edit the output file.

Plotting log normal density in R has wrong height

I have a log-normal density with a mean of -0.4 and standard deviation of 2.5.
At x = 0.001 the height is over 5 (I double checked this value with the formula for the log-normal PDF):
dlnorm(0.001, -0.4, 2.5)
5.389517
When I plot it using the curve function over the input range 0-6 it looks like with a height just over 1.5:
curve(dlnorm(x, -.4, 2.5), xlim = c(0, 6), ylim = c(0, 6))
When I adjust the input range to 0-1 the height is nearly 4:
curve(dlnorm(x, -.4, 2.5), xlim = c(0, 1), ylim = c(0, 6))
Similarly with ggplot2 (output not shown, but looks like the curve plots above):
library(ggplot2)
ggplot(data = data.frame(x = 0), mapping = aes(x = x)) +
stat_function(fun = function(x) dlnorm(x, -0.4, 2.5)) +
xlim(0, 6) +
ylim(0, 6)
ggplot(data = data.frame(x = 0), mapping = aes(x = x)) +
stat_function(fun = function(x) dlnorm(x, -0.4, 2.5)) +
xlim(0, 1) +
ylim(0, 6)
Does someone know why the density height is changing when the x-axis scale is adjusted? And why neither attempt above seems to reach the correct height? I tried this with just the normal density and this doesn't happen.
curves generates a set of discrete points in the range you give it. By default it generates n = 101 points, so there is a step problem. If you increase the number of points you will have almost the correct value:
curve(dlnorm(x, -.4, 2.5), xlim = c(0, 1), ylim = c(0, 6), n = 1000)
In the first case you propose curve generates 101 points in the interval x <- c(0,6), while in the second case generates 101 points in the interval x <- c(0,1), so the step is more dense

Is it possible to insert a line of no discrimination in ROC plot using ggroc?

I have created a ROC plot with multiple ROC-curves using ggroc based on pROC. How can I insert a line of no discrimination?
I would like to have a line of no discrimination from 0,0 to 1,1 in my plot, so that I can better visually evaluate my ROC-curves.
I have tried using the plot() function on my ggplot object, and I have tried using + geom_abline(), and the lines() function without any luck.
library(pROC)
#Creating curves and labeling)
ROC_curves <- ggroc(list(log=ROC_log, tree=ROC_tree, xgbt=ROC_xgbt), legacy.axes=TRUE)
ROC_curves2 <- ROC_curves + xlab("FPR") + ylab("TPR")
#but this part doesn't Work:
+ qplot(1,1) + geom_abline(intercept=0, slope=1)
I have also tried doing:
plot(ROC_curves2, identity=TRUE)
I would like a line of no discrimination going from 0,0 to 1,1 in my plot.
When adding qplot(1,1) + geom_abline(), I get "Error: Don't know how to add o to a plot".
When using plot() a plot is returned, but still with no line.
The ROC_curves already returns a ggplot plot. Adding a new plot to it with qplot is not possible nor necessary, just add geom_abline directly:
ROC_curves + xlab("FPR") + ylab("TPR") +
geom_abline(intercept = 0, slope = 1,
color = "darkgrey", linetype = "dashed")
The abline extends beyond the limits of the ROC curve. To avoid that you can use geom_segment instead:
ROC_curves + xlab("FPR") + ylab("TPR") +
geom_segment(aes(x = 0, xend = 1, y = 0, yend = 1),
color="darkgrey", linetype="dashed")
Also note that if you weren't using legacy.axes=TRUE you would need to have intercept = 1 so that the line crosses the 0 line on the top right.
... + geom_segment(aes(x = 0, xend = 1, y = 0, yend = 1)) # legacy.axes = TRUE
... + geom_segment(aes(x = 1, xend = 0, y = 0, yend = 1)) # legacy.axes = FALSE
#Calimo's solution didn't work for me but I think that is due to the size of my dataset so the graph won't render. Found a gitlab issue (https://github.com/tidyverse/ggplot2/issues/4190) about how annotate is much faster than geom_segment. I'm using the following:
+ annotate("segment",x = 1, xend = 0, y = 0, yend = 1, color="red", linetype="dashed")

Revise the number of ticks in the x-axis?

I only have a series of number, and I want to count the number of each element. Here is something I have done. X-axis is my element and Y-axis is the number of each element.
My question is, how could I revise the way of presentation in the x-axis? I only want to see 0.4, 0.5, 0.6, 0.7, 0.8 and 0.9 in the axis, but still to keep the same number of bars in the figure (nothing changed). Any suggestion please?
d1 <- ggplot(TestData, aes(factor(TestData$Col1)))
d2 <- d1 + geom_bar() + xlab("") + ylab("")
Create data with mean of 0.5, std of 0.2:
data<- rnorm(1000,0.5,0.2)
dataf <- data.frame(data)
Make histogram for all data range:
ggplot(aes(x = data),data = dataf) +
geom_histogram()
Xlim to 0.4 to 0.9:
ggplot(aes(x = data),data = dataf) +
geom_histogram() +
scale_x_continuous(limits = c(0.4,0.9),
breaks= scales::pretty_breaks(n=5))
In base graphics, you can just omit the axes when generating the plot, then add them manually using the axis function:
set.seed(1234)
dat <- rnorm(1000, 0.5, 0.1)
hist(dat, axes = FALSE, xlim = c(0, 1))
axis(side = 2)
axis(side = 1, at = seq(0.4, 0.9, 0.1))

Colours across Plots / Heatmaps in R

I am creating a number of heatmaps in R, but I am having problems when it comes to keeping the colour scale consistent across graphs.
I find that the colours are scaled within a graph, is there a way to make colours consistent across graphs? Ie. So that that colour difference between a value of 0.4 and 0.5 is always the same?
Code Example:
set.seed(123)
d1 = matrix(rnorm(9, mean = 0.2, sd = 0.1), ncol = 3)
d2 = matrix(rnorm(9, mean = 0.8, sd = 0.1), ncol = 3)
mat = list(d1, d2)
for(m in mat)
heatmap(m, Rowv = NA ,Colv = NA)
You'll note in the example that cell (2,3) the first graph is similar to cell (1,3) in the second, despite being ~0.8 different
Here's a way to do it with ggplot2, if you're open to not using base graphics:
library(reshape2)
library(ggplot2)
# Set common limits for color scale
limits = range(unlist(mat))
Here's the code for two separate graphs. The last line of code for each graph ensures that they use the same z limits for setting the colors:
ggplot(melt(mat[[1]]), aes(Var1, Var2, fill=value)) +
geom_tile() +
scale_fill_continuous(limits=limits)
ggplot(melt(mat[[2]]), aes(Var1, Var2, fill=value)) +
geom_tile() +
scale_fill_continuous(limits=limits)
Another option is to plot both heatmaps in a single graph using facetting, which automatically ensures both graphs are on the same color scale:
ggplot(melt(mat), aes(Var1, Var2, fill=value)) +
geom_tile() +
facet_grid(. ~ L1)
I've used the default colors here, but for either approach you can set the color scale to be anything you wish. For example:
ggplot(melt(mat), aes(Var1, Var2, fill=value)) +
geom_tile() +
facet_grid(. ~ L1) +
scale_fill_gradient(low="red", high="green")
You could use the image function directly (heatmap uses image), though it will require some extra formatting to match the output of heatmap. You can use zlim to set the color range. Quoting from the ?image page:
the minimum and maximum z values for which colors should be plotted,
defaulting to the range of the finite values of z. Each of the given
colors will be used to color an equispaced interval of this range. The
midpoints of the intervals cover the range, so that values just
outside the range will be plotted.
# define zlim min and max for all the plots
minz = Reduce(min, mat)
maxz = Reduce(max, mat)
for(m in mat) {
image( m, zlim = c(minz, maxz), col = heat.colors(20))
}
To get closer to the formatting produced by heatmap, you can just reuse some code from the heatmap function:
for(m in mat) {
labCol = dim(m)[2]
labRow = dim(m)[1]
image(seq_len(labCol), seq_len(labRow), m, zlim = c(minz, maxz),
col = heat.colors(20), axes = FALSE, xlab = "", ylab = "",
xlim = 0.5 + c(0, labCol), ylim = 0.5 + c(0, labRow))
axis(1, 1L:labCol, labels = seq_len(labCol), las = 2, line = -0.5, tick = 0)
axis(4, 1L:labRow, labels = seq_len(labRow), las = 2, line = -0.5, tick = 0)
}
Using the breaks argument to image is another option. It allows more flexibility than zlim in setting the breakpoints for colors. Quoting from the help page, breaks is
a set of finite numeric breakpoints for the colours: must have one
more breakpoint than colour and be in increasing order. Unsorted
vectors will be sorted, with a warning.

Resources