I am working with physical activity data and follow-up pain data. I have a large dataset but for the shake of this example I have created a small one with the variables of my interest.
As my physical activity data are compositional in nature I am using compositional data analysis before using those variables as predictors in my mixed-effects model. My goal is to use the predict() function to predict some new data that I have created but I am receiving the folowing:
Error in rep(0, nobs) : invalid 'times' argument
I have googled it and I saw a post that was posted a few year ago but the answer did not work for mine.
Below is the dataset and my code:
library("tidyverse")
library("compositions")
library("robCompositions")
library("lme4")
dataset <- structure(list(work = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 3L,
3L, 3L, 4L, 4L, 4L), .Label = c("1", "2", "3", "4"), class = "factor"),
department = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L,
3L, 4L, 4L, 4L), .Label = c("1", "2", "3", "4"), class = "factor"),
worker = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L,
4L, 4L, 4L), .Label = c("1", "2", "3", "4"), class = "factor"),
age = c(45, 43, 65, 45, 76, 34, 65, 23, 23, 45, 32, 76),
sex = structure(c(1L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 1L,
2L, 2L), .Label = c("1", "2"), class = "factor"), pain = c(4,
5, 3, 2, 0, 7, 8, 10, 1, 4, 5, 4), lpa_w = c(45, 65, 43,
76, 98, 65, 34, 56, 2, 3, 12, 34), mvpa_w = c(12, 54, 76,
87, 45, 23, 65, 23, 54, 76, 23, 54), lpa_l = c(54, 65, 34,
665, 76, 87, 12, 34, 54, 12, 45, 12), mvpa_l = c(12, 43,
56, 87, 12, 54, 76, 87, 98, 34, 56, 23)), class = "data.frame", row.names = c(NA,
-12L))
#create compositions of physical activity
dataset$comp_w <- acomp(cbind(lpa_w = dataset[,7],
mvpa_w = dataset[,8]))
dataset$comp_l <- acomp(cbind(lpa_l = dataset[,9],
mvpa_l = dataset[,10]))
#Make a grid to use for predictions for composition of lpa_w and mvpa_w
mygrid=rbind(
expand.grid(lpa_w = seq(min(2), max(98),5),
mvpa_w = seq(min(12), max(87), 5)))
griddata <- acomp(mygrid)
#run the model
model <- lmer(pain ~ ilr(comp_w) + age + sex + ilr(comp_l) +
(1 | work / department / worker),
data = dataset)
(prediction = predict(model, newdata = list(comp_w = griddata,
age = rep(mean(dataset$age, na.rm=TRUE),nrow(griddata)),
sex = rep("1", nrow(griddata)),
comp_l = do.call("rbind", replicate(n=nrow(griddata), mean(acomp(dataset[,12])), simplify = FALSE)),
work = rep(dataset$work, nrow(griddata)),
department = rep(dataset$department, nrow(griddata)),
worker = rep(dataset$worker, nrow(griddata)))))
Any help would be greatly appreciated.
Thanks
Assigning the results of acomp to an element of a data frame gives a weird data structure that messes things up downstream.
Constructing this data set (without messing up the original dataset):
dataset_weird <- dataset
dataset_weird$comp_w <- acomp(cbind(lpa_w = dataset[,7],
mvpa_w = dataset[,8]))
dataset_weird$comp_l <- acomp(cbind(lpa_l = dataset[,9],
mvpa_l = dataset[,10]))
The result is so weird that str(dataset_weird), the usual way of investigating the structure of an R object, fails with
$ comp_w :Error in unclass(x)[i, , drop = drop] :
(subscript) logical subscript too long
If we run sapply(dataset_weird, class) we see that these elements have class acomp. (They also appear to have an odd print() method: when we print(dataset_weird$comp_w) the results are a matrix of strings, but if we unclass(dataset_weird$comp_w) we can see that the underlying object is numeric [!])
This whole problem is kind of tricky since you're working with n-column matrices that are getting converted to special acomp() objects that are then getting converted to (n-1)-dimensional matrices (isometric log-ratio-transformed compositional data), the columns of which are then getting used as predictors. The basic point is that lme4's machinery will get confused if you have elements in your data frame that are not simple one-dimensional vectors. So you have to do the work of creating data frame columns yourself.
Here's what I came up with, with one missing piece (described below):
## utility function: *either* uses a matrix argument (`comp_data`)
## *or* extracts relevant columns from a data frame (`data`):
## returns ilr-transformed values as a matrix, with appropriate column names
ilr_dat <- function(data, suffix = NULL, comp_data = NULL) {
if (!is.null(suffix) && is.null(comp_data)) {
comp_data <- as.matrix(data[grep(paste0(suffix,"$"), names(data))])
}
ilrmat <- ilr(acomp(comp_data))
colnames(ilrmat) <- paste0("ilr", suffix, ".", 1:ncol(ilrmat))
return(ilrmat)
}
## augment original data set (without weird compositional elements)
## using data.frame() rather than $<- or rbind() collapses matrix arguments
## to data frame rows in a way that R expects
dataset2 <- data.frame(dataset, ilr_dat(dataset, "_l"))
dataset2 <- data.frame(dataset2, ilr_dat(dataset, "_w"))
mygrid <- rbind(
expand.grid(lpa_w = seq(min(2), max(98),5),
mvpa_w = seq(min(12), max(87), 5)))
## generate ilr data for prediction
griddata <- as.data.frame(ilr_dat(comp_data=mygrid, suffix="_w"))
#run the model: ilr(comp_l) **not** included, see below
model <- lmer(pain ~ ilr_w.1 + age + sex + ## ilr(comp_l) +
(1 | work / department / worker),
data = dataset2)
## utility function for replication
xfun <- function(s) rep(dataset[[s]], nrow(griddata))
predict(model, newdata = data.frame(griddata,
age = mean(dataset$age, na.rm=TRUE),
sex = "1",
work = xfun("work"),
department = xfun("department"),
worker = xfun("worker")))
This seems to work.
The reason I did not include the _l composition/irl in the model or the predictions is that I couldn't understand what this statement was doing:
comp_l = do.call("rbind", replicate(n=nrow(griddata), mean(acomp(dataset[,12])), simplify = FALSE))
Related
I want to generate multiple forest plots using the forestplot() function in R (from the package R/forestplot), and want to ensure that I can line up the text and graph sections in each so they can be usefully shown as a stacked plot, like this:
(taken from R forestplot package blank lines with section headings)
but with the possibility that there may be different scales in each subplot, lining up the zero effect line in each plot.
Which attributes need changing in the forestplot() call to ensure that this occurs?
EDIT: to provide a minimum code example
library(forestplot)
library(tidyr)
cohort <- data.frame(Age = c(43, 39, 34, 55, 70, 59, 44, 83, 76, 44,
75, 60, 62, 50, 44, 40, 41, 42, 37, 35, 55, 46),
Status = structure(c(2L,
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 1L, 2L, 1L, 2L, 2L, 2L, 1L, 1L,
1L, 1L, 1L, 2L, 2L), levels = c("-", "+"), class = "factor"),
Group = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L,
1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L), levels = c("1","2"), class = "factor"))
age.lm <- lm(Age ~ Group, data = cohort)
status.lm <- glm(Status ~ Group, data = cohort, family=binomial(link=logit))
age.data <- summary(age.lm)$coefficients[2,]
status.data <- summary(status.lm)$coefficients[2,]
age.data <- rbind(c(0,0,0,1,"Group 1", "n=15"),
c(age.data[1], age.data[1]-age.data[2]*1.95, age.data[1]+age.data[2]*1.95, age.data[4], "Group 2", "n=7"))
status.data <- rbind(c(0,0,0,1,"Group 1", "[+13,-2]"),
c(status.data[1], status.data[1]-status.data[2]*1.95, status.data[1]+status.data[2]*1.95, status.data[4], "Group 2", "[+2,-5]"))
colnames(age.data) <- c("mean","lower","upper","p-val","labeltext","numbers")
colnames(status.data) <- c("mean","lower","upper","p-val","labeltext","numbers")
age.data <- data.frame(age.data)
status.data <- data.frame(status.data)
age.data$mean <- as.numeric(age.data$mean)
age.data$lower <- as.numeric(age.data$lower)
age.data$upper <- as.numeric(age.data$upper)
status.data$mean <- exp(as.numeric(status.data$mean))
status.data$lower <- exp(as.numeric(status.data$lower))
status.data$upper <- exp(as.numeric(status.data$upper))
age.plot <- forestplot(age.data,
labeltext = c(labeltext,numbers),
boxsize = 0.1,
xlog = FALSE,
clip=c(-20,20),
xticks=c(-20,-10,0,10,20),
txt_gp = fpTxtGp(ticks=gpar(cex=1)),
align=c("l","c","l"))
status.plot <- forestplot(status.data,
labeltext = c(labeltext,numbers),
boxsize = 0.1,
xlog = TRUE,
clip=c(1/100,100),
xticks=c(log(1e-2),log(1e-1),0,log(1e1),log(1e2)),
txt_gp = fpTxtGp(ticks=gpar(cex=1)),
align=c("l","c","l"))
Note that the age plot is a linear model and the status plot is a logistic model:
I want to be able to arrange the relative sizes of the text to the left and the plot to the right in order that the zero-effect lines (at 0 and at 1 respectively) line up so that the forest plots stack cleanly.
With the align argument you could left "l" align the parts of your plot, so the text can be left aligned. If you want to align your zero-effect lines you could use mar and play with the units to adjust one of the graphs. Here is a reproducible example:
library(forestplot)
library(tidyr)
age.plot <- forestplot(age.data,
labeltext = c(labeltext,numbers),
boxsize = 0.1,
xlog = FALSE,
clip=c(-20,20),
xticks=c(-20,-10,0,10,20),
txt_gp = fpTxtGp(ticks=gpar(cex=1)),
align=c("l","l","l")
)
status.plot <- forestplot(status.data,
labeltext = c(labeltext,numbers),
boxsize = 0.1,
xlog = TRUE,
clip=c(1/100,100),
xticks=c(log(1e-2),log(1e-1),0,log(1e1),log(1e2)),
txt_gp = fpTxtGp(ticks=gpar(cex=1)),
align=c("l","l","l"),
mar = unit(c(0,5,0,10.5), "mm")
)
library(grid)
grid.newpage()
pushViewport(viewport(layout = grid.layout(2, 1)))
pushViewport(viewport(layout.pos.row = 1))
plot(age.plot)
popViewport()
pushViewport(viewport(layout.pos.row = 2))
plot(status.plot)
popViewport(2)
Created on 2022-12-28 with reprex v2.0.2
So I am trying to create percentages to display on a plot.
Here is my dataset:
how_often_ByYear <- structure(list(Var1 = structure(c(1L, 2L, 3L, 1L, 2L, 3L), levels = c("A few times a year",
"Never been", "Once or twice"), class = "factor"), Var2 = structure(c(1L,
1L, 1L, 2L, 2L, 2L), levels = c("Year 1", "Year 2"), class = "factor"),
Freq = c(0, 122, 47, 1, 117, 50), percent = c(0, 72, 28,
1, 69, 30)), class = "data.frame", row.names = c(NA, -6L))
And here is my code:
how_often_ByYear <- Visitor_Data_ByYear %>%
dplyr::select(How_often_have_you_visited_us, Year) #selects column for question 16
#mutate_all(funs(gsub("[[:punct:]]", "", .))) #removes annoying symbols
how_often_ByYear <- table(how_often_ByYear$How_often_have_you_visited_us, how_often_ByYear$Year)
how_often_ByYear <- as.data.frame(how_often_ByYear)
how_often_ByYear <- how_often_ByYear %>%
mutate(percent = Freq/sum(Freq)*100) %>%
mutate_if(is.numeric, round, 0)
View(how_often_ByYear)
right now, the numbers include both year 1 and year 2, so my percentages add up to around 50 percent. How do I separate the percentages for each year so that I can report on both?
Thanks in advance for your help.
I am making a geom_col in ggplot2. The x-axis is a numerical vector of timepoints (0, 6, 18, 24, 32, 44). There is a difference between each column corresponding to the numerical difference between each timepoint. But i want an equal distance between all the columns. I have searched for answers in here, but i didn't find a similar issue.
This is my code:
ggplot(data = ny_dataframe_scratch, aes(x=timepoint, y = relative_wound_healing, fill = Condition)) +
geom_col(width = 5, position = position_dodge()) +
scale_x_continuous(breaks=c(0, 6, 18, 24, 32, 44), name = "Time point, hours") +
scale_y_continuous(name = "Relative scratch area") +
scale_fill_manual(values=c("palevioletred4", "slategray")) +
geom_point(data = ny_dataframe_scratch, position = position_dodge(width = 5), aes(x=timepoint, y=relative_wound_healing, fill = Condition))
This is the output of dput():
structure(list(timepoint = c(0, 0, 0, 0, 0, 0, 6, 6, 6, 6, 6,
6, 18, 18, 18, 18, 18, 18, 24, 24, 24, 24, 24, 24, 32, 32, 32,
32, 32, 32, 44, 44, 44, 44, 44, 44), Condition = structure(c(2L,
2L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 1L, 1L,
1L, 2L, 2L, 2L, 1L, 1L, 1L, 2L, 2L, 2L, 1L, 1L, 1L, 2L, 2L, 2L,
1L, 1L, 1L), .Label = c("Control", "Knockout"), class = "factor"),
relative_wound_healing = c(1, 1, 1, 1, 1, 1, 0.819981, 0.78227,
0.811902, 0.873852, 0.893572, 0.910596, 0.39819, 0.436948,
0.559486, 0.534719, 0.591295, 0.612154, 0.222731, 0.2592,
0.453575, 0.37238, 0.477891, 0.505393, 0.05243246, 0.0809449,
0.2108063, 0.261122, 0.3750218, 0.4129873, 0, 0.0240122,
0.0778219, 0.0806758, 0.2495444, 0.3203724)), class = "data.frame", row.names = c(NA,
-36L))
Picture of how the graph looks:
The x-scale has proportional gaps because ‘ggplot2’ considers the values as continuous rather than categorical.
To make it categorical, you can for instance use factors:
aes(x = factor(timepoint, ordered = TRUE), …
(Without ordered = TRUE, ‘ggplot2’ assumes alphabetical ordering, so it would put 11 before 5, which probably isn’t what you want.)
To fix the bar heights, you need to compute and plot a summary statistic — ‘ggplot2’ allows you to do this using stat_summary (instead of geom_col):
stat_summary(fun.y = mean, geom = "col", position = position_dodge())
Taken together:
ggplot(ny_dataframe_scratch) +
aes(x = factor(timepoint, ordered = TRUE), y = relative_wound_healing, fill = Condition) +
scale_fill_manual(values = c("palevioletred4", "slategray")) +
stat_summary(fun.y = mean, geom = "col", position = position_dodge()) +
geom_point(position = position_dodge(width = 1)) +
labs(x = "Time point, hours", y = "Relative scratch area")
Your timepoints are "numeric". Try coercing them to factor. At that point, ggplot should plot them at equidistance from each other.
xy$timepoint <- as.factor(xy$timepoint)
I created a boxplot showing the dispersal distance $dist of some species $spe, and I would like the width of the boxes to be proportional to the density of regeneration of these species. I used "varwidth" and weight aesthetic as shown below, but this is still not correct, as it is still proportional to the number of observations and not only to the density of regeneration...
(for the density, I calculated the proportion for each species, so it goes from 10 to 100. It is given in the column data_dist2$prop2)
p <- ggplot(data_dist2, aes(x = reorder(spe, prop2), y = dist)) +
coord_flip() +
geom_boxplot(varwidth = TRUE, alpha=0.3, aes(weight=data_dist2$prop2), fill='grey10')
Would you have any idea how to make the boxplot exactly proportional to my prop2 column?
Reproductive example :
structure(list(spe = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L,
1L, 1L, 1L, 1L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L,
3L, 3L, 3L, 3L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L,
2L), .Label = c("Abies concolor", "Picea abies", "Sequoia semp."
), class = "factor"), dist = c(0, 0, 3, 3, 4, 4, 25, 46, 59,
113, 113, 9, 12, 12, 12, 15, 22, 22, 22, 22, 35, 35, 36, 49,
85, 85, 90, 5, 5, 1, 1, 8, 13, 48, 48, 52, 52, 52, 65, 89), prop2 = c(92.17,
92.17, 92.17, 92.17, 92.17, 92.17, 92.17, 92.17, 92.17, 92.17,
92.17, 10.9, 10.9, 10.9, 10.9, 10.9, 10.9, 10.9, 10.9, 10.9,
10.9, 10.9, 10.9, 10.9, 10.9, 10.9, 10.9, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 100, 100, 100)), row.names = c(NA,
-40L), class = "data.frame")
Weight doesn't seem to be designed exactly for this, but you can hack it a bit. First note that the weight given to each group is the sum of the weights of the observations, so if you have a different number of observation for each species then you may need to change prop2 to the current value divided by the number of observations in the group. (I can't tell from your example if this applies)
Then note that the width is proportional to the square root of the weight, so change your code to reverse that with:
p <- ggplot(data_dist2, aes(x = reorder(spe, prop2), y = dist)) +
coord_flip() +
geom_boxplot(varwidth = TRUE, alpha=0.3, aes(weight=data_dist2$prop2^2), fill='grey10')
Miff beats me to it, but anyway here's my answer. As Miff said, you can weight the width by your prop2.
ggplot(data_dist2, aes(x = reorder(spe, prop2), y = dist)) +
geom_boxplot(aes(weight = prop2),
varwidth = TRUE,
fill='grey10', alpha=0.3) +
coord_flip()
But geom_boxplot() implicitly takes the sample size into account. So you need to divide that away in your weights. Here's how you can do it with data.table.
library(data.table)
setDT(data_dist2) # convert to data.table
data_dist2[, weight := prop2 / .N, by = spe] # Divide prop2 by sample size for each species
ggplot(data_dist2, aes(x = reorder(spe, prop2), y = dist)) +
geom_boxplot(aes(weight = weight), # note weight = weight, not weight = prop2
varwidth = TRUE,
fill='grey10', alpha=0.3) +
coord_flip()
I am thinking how to convert string Date data of tall array format to Date and organise the ggplot by it in the x-axis by scale_x_date.
Pseudocode motivated by Henrik's proposal
Change string data format to as.Date, maybe something similar to the following in ggplot's parameter x = ...
as.Date(time.data, format("%d.%m.%Y")
Apply scale_x_date in ggplot with date_breaks("2 day")
Code with dummy data data3
library("ggplot2")
# For RStudio
options(device="pdf") # https://stackoverflow.com/questions/6535927/how-do-i-prevent-rplots-pdf-from-being-generated
filename.pdf <- paste0(getwd(), "/", "Rplots", ".pdf", sep = "")
pdf(file=filename.pdf)
# Dummy data
data3 <- structure(list(Time.data = c("16.7.2017", "15.7.2017",
"14.7.2017", "13.7.2017", "12.7.2017", "11.7.2017", "9.7.2017",
"7.7.2017", "6.7.2017", "5.7.2017", "4.7.2017", "3.7.2017", "2.7.2017",
"1.7.2017", "30.6.2017", "29.6.2017", "28.6.2017", "16.7.2017",
"15.7.2017", "14.7.2017", "13.7.2017", "12.7.2017", "11.7.2017",
"9.7.2017", "7.7.2017", "6.7.2017", "5.7.2017", "4.7.2017", "3.7.2017",
"2.7.2017", "1.7.2017", "30.6.2017", "29.6.2017", "28.6.2017",
"16.7.2017", "15.7.2017", "14.7.2017", "13.7.2017", "12.7.2017",
"11.7.2017", "9.7.2017", "7.7.2017", "6.7.2017", "5.7.2017",
"4.7.2017", "3.7.2017", "2.7.2017", "1.7.2017", "30.6.2017",
"29.6.2017", "28.6.2017"), variable = structure(c(1L, 1L, 1L,
1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L,
2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 3L,
3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L, 3L
), .Label = c("ave_max", "ave", "lepo"), class = "factor"),
value = c(69, 75, 83, 97, 101, 73, 77, 78, 98, 79, 78, 95,
70, 81, 78, 71, 72, 58, 59, 59, 58, 54, 56, 60, 60, 62, 58,
56, 63, 58, 58, 63, 58, 56, 48, 51, 51, 48, 48, 48, 52, 53,
52, 49, 48, 53, 50, 50, 54, 46, 47)), row.names = c(NA, -51L
), .Names = c("Time.data", "variable", "value"), class = "data.frame")
#Relevant part of the code based on Henrik's proposal,
#rejected timestamp approach which output has wrongly shown x-axis label in Fig. 1
p <- ggplot(data3, aes(x = as.Date(Time.data, format = "%d.%m.%Y"), y = value, fill = variable)) +
geom_bar(stat='identity') +
theme(axis.text.x = element_text(angle = 90, hjust=1),
text = element_text(size=10)) +
scale_x_discrete("Date") +
scale_x_date(date_breaks = "2 days", date_labels = "%d.%m.%Y")
print(p)
dev.off()
Output which I do not understand
Scale for 'x' is already present. Adding another scale for 'x', which will replace the existing scale.
Fig. 1 Output based on Henrik's proposal
Expected output: as such but with correct x-label there on the x-axis
OS: Debian 9
R: 3.4.0
RStudio: 1.0.143
Other sources: Date format for subset of ticks on time axis, scale_datetime shifts x axis, Time series plot gets offset by 2 hours if scale_x_datetime is used
You have specified two different scales for the x axis, a discrete scale and a continuous date scale, presumably in an attempt to rename the label on the x axis. For this, xlab() can be used:
library(ggplot2)
ggplot(data3, aes(x = as.Date(Time.data, format = "%d.%m.%Y"), y = value, fill = variable)) +
# use new geom_col() instead of geom_bar(stat = "identity")
# see http://ggplot2.tidyverse.org/articles/releases/ggplot2-2.2.0.html#stacking-bars
geom_col() +
theme(axis.text.x = element_text(angle = 90, hjust=1),
text = element_text(size=10)) +
# specify label for x axis
xlab("Time.date") +
scale_x_date(date_breaks = "2 days", date_labels = "%d.%m.%Y")
Alternatively, you can use the name parameter to scale_x_date():
ggplot(data3, aes(x = as.Date(Time.data, format = "%d.%m.%Y"), y = value, fill = variable)) +
geom_col() +
theme(axis.text.x = element_text(angle = 90, hjust=1),
text = element_text(size=10)) +
scale_x_date(name = "Time.date", date_breaks = "2 days", date_labels = "%d.%m.%Y")
Addendum: Saving plots
If the intention is to save just one plot in a file you can add a call to ggsave() after the call to ggplot(), i.e.,
ggplot(...
ggsave("Rplots.pdf")
instead of
options(device="pdf") # https://stackoverflow.com/questions/6535927/how-do-i-prevent-rplots-pdf-from-being-generated
filename.pdf <- paste0(getwd(), "/", "Rplots", ".pdf", sep = "")
pdf(file=filename.pdf)
p <- ggplot(...
print(p)
dev.off()
According to help("ggsave")
ggsave() is a convenient function for saving a plot. It defaults to
saving the last plot that you displayed, using the size of the current
graphics device. It also guesses the type of graphics device from the
extension.
Another issue is the creation of the file path. Instead of
filename.pdf <- paste0(getwd(), "/", "Rplots", ".pdf", sep = "")
it is better to use
filename.pdf <- file.path(getwd(), "Rplots.pdf")
which constructs the path to a file from components in a platform-independent way.