Related
I am working with the R programming language. I am trying to make a "parallel coordinates plot" using some fake data:
library(MASS)
a = rnorm(100, 10, 10)
b = rnorm(100, 10, 5)
c = rnorm(100, 5, 10)
d = matrix(a, b, c)
parcoord(d[, c(3, 1, 2)], col = 1 + (0:149) %/% 50)
However, a problem arises when I try to mix numeric and factor variables together:
group <- sample( LETTERS[1:4], 100, replace=TRUE, prob=c(0.25, 0.25, 0.25, 0.25) )
d = matrix(a,b, group)
parcoord(d[, c(3, 1, 2)], col = 1 + (0:149) %/% 50)
Error in x - min(x, na.rm = TRUE): non-numeric argument to binary operator
I am just curious. Can this problem be resolved? Or is it simply impossible to make such a plot using numeric and factor variables together?
I saw a previous stackoverflow post over here where a similar plot is made using numeric and factor variables: How to plot parallel coordinates with multiple categorical variables in R
However, I am using a computer with no USB port or internet access - I have a pre-installed version of R with limited libraries (I have plotly, ggplot2, dplyr, MASS ... I don't have ggally or tidyverse) and was looking for a way to do this only with the parcoord() function.
Does anyone have any ideas if this can be done?
Thanks
Thanks
One option is to label rows of the matrix using a factor and use that on the plot, e.g.
library(MASS)
set.seed(300)
par(xpd=TRUE)
par(mar=c(4, 4, 4, 6))
a = rnorm(12, 10, 10)
b = rnorm(12, 10, 5)
c = rnorm(12, 5, 10)
group <- sample(c("#FF9289", "#FF8AFF", "#00DB98", "#00CBFF"),
12, replace=TRUE)
d = cbind(a, b, c)
rownames(d) <- group
parcoord(d[, c(3, 1, 2)], col = group)
title(main = "Plot", xlab = "Variable", ylab = "Values")
axis(side = 2, at = seq(0, 1, 0.1),
tick = TRUE, las = 1)
legend(3.05, 1, legend = c("A", "B", "C", "D"), lty = 1,
col = c("#FF9289", "#FF8AFF", "#00DB98", "#00CBFF"))
EDIT
Thanks for the additional explanation. What you want does make sense, but unfortunately it doesn't look like it will work as I expected. I tried to make a plot using an ordered factor as the middle variable (per https://pasteboard.co/JKK4AUD.jpg) but got the same error ("non-numeric argument to binary operator").
One way I thought of doing it is to recode the factor as a number (e.g. "Var_1" -> 0.2, "Var_2" -> 0.4) as below:
library(MASS)
set.seed(123)
par(xpd=TRUE)
par(mar=c(4, 4, 4, 6))
a = rnorm(12, 10, 10)
b = c(rep("Var_1", 3),
rep("Var_2", 3),
rep("Var_3", 3),
rep("Var_4", 3))
c = rnorm(12, 5, 10)
group <- c(rep("#FF9289", 3),
rep("#FF8AFF", 3),
rep("#00DB98", 3),
rep("#00CBFF", 3))
d = data.frame("A" = a,
"Factor" = b,
"C" = c,
"Group" = group)
d$Factor <- sapply(d$Factor, switch,
"Var_1" = 0.8,
"Var_2" = 0.6,
"Var_3" = 0.4,
"Var_4" = 0.2)
parcoord(d[, c(1, 2, 3)], col = group)
title(main = "Plot", xlab = "Variable", ylab = "Values")
axis(side = 2, at = seq(0, 1, 0.1),
tick = TRUE, las = 1)
legend(3.05, 1, legend = c("A", "B", "C", "D"), lty = 1,
col = c("#FF9289", "#FF8AFF", "#00DB98", "#00CBFF"))
mtext(text = "Var 1", side = 1, adj = 0.6, padj = -30)
mtext(text = "Var 3", side = 1, adj = 0.6, padj = -12)
mtext(text = "Var 2", side = 1, adj = 0.6, padj = -21)
mtext(text = "Var 4", side = 1, adj = 0.6, padj = -3)
I was wondering given the R code for the plot below, how I can get a png file of this plot such that the plot fills all the image with no margin left?
What I have tried so far was playing with mar and oma with no success:
N = 20 ; df = N-1
par(oma = rep(0, 4), mar = rep(0, 4))
png("plot.png", width = 4, height = 5, units = "in", res = 500)
BB = curve( dt(x*sqrt(N), df)*sqrt(N), -1, 1, n = 1e4, xlab = "d",
ylab = NA, font = 2, font.lab = 2, type = "n", yaxt = "n", bty = "n", mgp = c(2, 1, -.5))
polygon(BB, col = rgb(1, 0, 0, .4), border = NA)
dev.off()
Finally, the following worked for me with the help of one of the colleagues from SO:
N = 20 ; df = N-1
par(oma = rep(0, 4), mar = c(2.5, .01, 0, .01), mgp = c(1.5, .3, 0), xpd = NA)
BB = curve( dt(x*sqrt(N), df)*sqrt(N), -1, 1, n = 1e4, xlab = "d",
ylab = NA, font = 2, font.lab = 4, type = "n", yaxt = "n",
bty = "n", cex.axis = .7, cex.lab = .9)
polygon(BB, col = rgb(1, 0, 0, .4), border = NA)
dev.copy(png, "plot.png", width = 2, height = 3, units = "in", res = 500)
dev.off()
dev.off()
Is it possible to add a 2d plot to a 3D plot in R?
I have the following R code that generates a 3d bar plot.
dt = structure(c(1, 1, 1, 3,
0, 2, 2, 1,
1, 2, 1, 3,
2, 6, 3, 1,
1, 2, 3, 0,
1, 0, 2, 1,
1,2,2,2), .Dim = c(4L, 7L), .Dimnames = list(c("0-50",
"51-60", "61-70", "71-80"
), c("0-50", "51-60", "61-70", "71-80", "81-90", "91-100", "101-Inf")))
m <- matrix(rep(seq(4),each=7), ncol=7, nrow=4, byrow = TRUE)
hist3D(x = 1:4, z = dt, scale = T,bty="g", phi=35,theta=30,border="black",space=0.15,col = jet.col(5, alpha = 0.8), add = F, colvar = m, colkey = F, ticktype = "detailed")
The hist3d call only:
hist3D(x = 1:4, z = dt, scale = T,bty="g", phi=35,theta=30, border="black", space=0.15,col = jet.col(5, alpha = 0.8), add = F, colvar = m, colkey = F, ticktype = "detailed")
This generates the following 3d plot:
What I'm looking for is being able to add a plot in the position where the grey grid is. Is it possible?
Thanks!
As far as I know, there isn't a good function to make a barplot in RGL. I suggest a manual method.
dt = structure(c(1, 1, 1, 3,
0, 2, 2, 1,
1, 2, 1, 3,
2, 6, 3, 1,
1, 2, 3, 0,
1, 0, 2, 1,
1,2,2,2), .Dim = c(4L, 7L), .Dimnames = list(c("0-50",
"51-60", "61-70", "71-80"
), c("0-50", "51-60", "61-70", "71-80", "81-90", "91-100", "101-Inf")))
Making 3D barplot in RGL
library(rgl)
# make dt xyz coordinates data
dt2 <- cbind( expand.grid(x = 1:4, y = 1:7), z = c(dt) )
# define each bar's width and depth
bar_w <- 1 * 0.85
bar_d <- 1 * 0.85
# make a base bar (center of undersurface is c(0,0,0), width = bar_w, depth = bar_d, height = 1)
base <- translate3d( scale3d( cube3d(), bar_w/2, bar_d/2, 1/2 ), 0, 0, 1/2 )
# make each bar data and integrate them
bar.list <- shapelist3d(
apply(dt2, 1, function(a) translate3d(scale3d(base, 1, 1, a[3]), a[1], a[2], 0)),
plot=F)
# set colors
for(i in seq_len(nrow(dt2))) bar.list[[i]]$material$col <- rep(jet.col(5)[c(1:3,5)], 7)[i]
open3d()
plot3d(0,0,0, type="n", xlab="x", ylab="y", zlab="z", box=F,
xlim=c(0.5, 4.5), ylim=c(0.5, 7.5), zlim=c(0, 6.2), aspect=T, expand=1)
shade3d(bar.list, alpha=0.8)
wire3d(bar.list, col="black")
light3d(ambient="black", diffuse="gray30", specular="black") # light up a little
Add a 2d plot
# show2d() uses 2d plot function's output as a texture
# If you need the same coordinates of 2d and 3d, plot3d(expand=1) and show2d(expand=1),
# the same xlims, equivalent plot3d(zlim) to 2d plot's ylim, 2d plot(xaxs="i", yaxs="i") are needed.
show2d({
par(mar = c(0,0,0,0))
barplot(c(3,4,5,6), yaxs="i", ylim=c(0, 6.2))
},
expand = 1 , face = "y+", texmipmap = F) # texmipmap=F makes tone clear
Can you suggest a way to add a main title to that graph? The gridBulletGraphV function can be found here.
ytd2005 <- data.frame(
measure=c("Revenue", "Profit", "Avg Order Size", "New Customers", "Cust Satisfaction"),
units=c("U.S. $ (1,000s)", "%", "U.S. $", "Count", "Top Rating of 5"),
low=c(150, 20, 350, 1400, 3.5),
mean=c(225, 25, 500, 2000, 4.25),
high=c(300, 30, 600, 2500, 5),
target=c(250, 26, 550, 2100, 4.2),
value=c(275, 22.5, 310, 1700, 4.5)
)
nticks <- c(7, 7, 7, 6, 7)
format <- c("s", "p", "s", "k", "s")
col1 <- c("#a5a7a9", "#c5c6c8", "#e6e6e7")
gridBulletGraphV(ytd2005, nticks=nticks, format=format, bcol=col1, font=11, scfont=9)
You can do that by changing the function. I added a ptitle="text" parameter to the function and added the following code just before for (i in 1:n) {:
# Title
vp <- viewport(layout.pos.row = 1)
pushViewport(vp)
grid.text(label = ptitle,
just = "centre",
gp = gpar(fontsize=font*1.5, col="black", fontface="bold"),
x = .5,
y = 0.1)
upViewport()
You can now call the function with:
gridBulletGraphV(ytd2005, nticks=nticks, format=format, bcol=col1, font=11,
scfont=9, ptitle="Plot Title")
which gives the following result:
The revised gridBulletGraphV function:
gridBulletGraphV <- function(bgData, nticks=3, format="s", bcol=c("red", "yellow", "green"), tcol="black", vcol="black", font=25, scfont=15, ptitle="text") {
# Data Prep
n <- nrow(bgData)
nam <- c("low", "mean", "high", "target", "value")
datMat <- as.matrix(bgData[, nam])
# Nticks/Format Prep
if (length(nticks) == 1) {
nticks <- rep(nticks, n)
}
if (length(format) == 1) {
format <- rep(format, n)
}
# Layout
hl <- rep(1, n + 2)
hu <- c("lines", rep("null", n), "lines")
layout <- grid.layout(4, n + 2, widths = unit(hl, hu),
heights = unit(c(1, 1, 5, 2), c("lines", "null", "null", "lines")))
# Set Layout
grid.newpage()
pushViewport(plotViewport(c(0, 0, 0, 0), layout = layout))
# Title
vp <- viewport(layout.pos.row = 1)
pushViewport(vp)
grid.text(label = ptitle,
just = "centre",
gp = gpar(fontsize=font*1.5, col="black", fontface="bold"),
x = .5,
y = 0.1)
upViewport()
for (i in 1:n) {
#
vp <- viewport(layout.pos.row = 3,
layout.pos.col = i+1)
pushViewport(vp)
# Sublayout
subLayout <- grid.layout(nrow = 1,
widths = unit(c(1, 2, 1), c("null", "null", "null")),
ncol = 3)
pushViewport(plotViewport(c(0, 0, 0, 0), layout=subLayout))
vp <- viewport(layout.pos.row = 1,
layout.pos.col = 2,
yscale = c(0, datMat[i, 3]))
pushViewport(vp)
# x-Axis Labels
# Formatierung Label
if (format[i] == "s") {
brks <- labels <- round(seq(0, datMat[i, 3], length=nticks[i]), 0)
} else if (format[i] == "p"){
brks <- labels <- round(seq(0, datMat[i, 3], length=nticks[i]), 0)
labels <- paste0(labels, "%")
} else if (format[i] == "k") {
brks <- labels <- round(seq(0, datMat[i, 3], length=nticks[i]), 0)
labels <- format(labels, digits=10, nsmall=0, decimal.mark=".", big.mark=",")
}
grid.yaxis(at=brks, label=labels, gp=gpar(fontsize=scfont, col="black", fontface="bold"))
grid.rect(y = c(0, datMat[i, 1:2]) / datMat[i, 3],
height = unit(diff(c(0, datMat[i, 1:3])), "native"),
x = rep(0.5, 3),
width = 1,
just = "bottom",
gp = gpar(fill=bcol, col=bcol))
grid.rect(y = c(0, datMat[i, 5]),
height = unit(diff(c(0, datMat[i, 5])), "native"),
x = 0.5,
width = 0.5,
gp = gpar(fill=vcol, col=vcol), just="bottom")
a <- datMat[i, 1] * 0.01
grid.rect(y = datMat[i, 4] / datMat[i, 3],
height = unit(a, "native"),
x = 0.5,
width = 0.8,
gp = gpar(fill=tcol, col=tcol), just="bottom")
upViewport(n=3)
# Annotation
pushViewport(plotViewport(c(0, 0, 0, 0), layout=layout))
vp <- viewport(layout.pos.row = 2,
layout.pos.col = i+1)
pushViewport(vp)
# Sublayout 1: Same layout as graph
subLayout <- grid.layout(nrow = 1,
ncol = 3,
widths = unit(c(1, 2, 1), c("null", "null", "null")))
pushViewport(plotViewport(c(0, 0, 0, 0), layout=subLayout))
vp <- viewport(layout.pos.row = 1,
layout.pos.col = 2)
pushViewport(vp)
# Sublayout 2: two rows of text; centred middle of graph
subLayout <- grid.layout(nrow = 3,
ncol = 1,
widths = unit(c(1, 1), c("null", "null")))
pushViewport(plotViewport(c(0, 0, 0, 0), layout=subLayout))
# First Text: Measure
vp <- viewport(layout.pos.row = 2,
layout.pos.col = 1)
pushViewport(vp)
grid.text(label = bgData$measure[i],
just = "bottom",
gp = gpar(fontsize=font, col="black", fontface="bold"),
x = .5,
y = 0.1)
upViewport()
# Second Text: Unit
vp <- viewport(layout.pos.row = 3,
layout.pos.col = 1)
pushViewport(vp)
grid.text(label = bgData$units[i],
just = "bottom",
gp = gpar(fontsize=font, col="black"),
x = .5,
y = .5)
upViewport(n=5)
}
}
I created an age-sex-pyramid using gplots. I would like to center a subtitle between the two sides of the pyramid.
However, I can only get the subtitle aligned with one of the two sides of the pyramid:
library(gplots)
agetable <- as.data.frame(cbind (c(2, 4, 7, 8, 10, 8, 6, 4, 2, 1),
c(1, 3, 5, 9, 11, 6, 4, 1, 0, 1)))
names(agetable) <- c("Male", "Female")
maxdir <- max(agetable)
subtitle <- "34% of data are mising age or sex"
agegraph.general.bysex <- function(agetable, maxdir, varname, miss){
agegroups <-
if (varname=='Male') {
datavec <- agetable[,'Male']
lim = c(maxdir,0)
agelabels = c('0-9','10-19','20-29','30-39','40-49','50-59',
'60-69','70-79','80-89','90+')
} else {
datavec <- agetable[,'Female']
lim = c(0, maxdir)
agelabels = ''
}
barplot2(
datavec, horiz=TRUE, space=0, xlab = varname, xlim=lim, col='grey85',
axisnames=TRUE, cex.axis = 1, cex.names = 1, names.arg=agelabels,
plot.grid=TRUE, cex.sub = 0.8, sub = miss)
}
layout(matrix(1:2, ncol=2, nrow=1))
par(las=1, adj=0.5, omi=c(0.1, 0.1, 0.1 ,0.1), cex=0.8 , cex.lab=0.8)
par(mar=c(7,5,0,0))
agegraph.general.bysex(agetable, maxdir, 'Male', subtitle)
par(mar=c(7,0,0,5))
agegraph.general.bysex(agetable, maxdir, 'Female', '')
I would be grateful for any suggestions!
what about
mtext(subtitle,outer=T,side=1,line=-1)