copy factor level order from one column to another - r

I have two columns in a data.frame, that should have levels sorted in the same order, but I don't know how to do it in a straightforward manner.
Here's the situation:
library(ggplot2)
library(dplyr)
library(magrittr)
set.seed(1)
df1 <- data.frame(rating = sample(c("GOOD","BAD","AVERAGE"),10,T),
div = sample(c("A","B","C"),10,T),
n = sample(100,10,T))
# I'm adding a label column that I use for plotting purposes
df1 <- df1 %>% group_by(rating) %>% mutate(label = paste0(rating," (",sum(n),")")) %>% ungroup
# # A tibble: 10 x 4
# rating div n label
# <fctr> <fctr> <int> <chr>
# 1 BAD C 48 BAD (220)
# 2 BAD B 87 BAD (220)
# 3 BAD C 44 BAD (220)
# 4 GOOD B 25 GOOD (77)
# 5 AVERAGE B 8 AVERAGE (117)
# 6 AVERAGE C 10 AVERAGE (117)
# 7 AVERAGE A 32 AVERAGE (117)
# 8 GOOD B 52 GOOD (77)
# 9 AVERAGE C 67 AVERAGE (117)
# 10 BAD C 41 BAD (220)
# rating levels are sorted
df1$rating <- factor(df1$rating,c("BAD","AVERAGE","GOOD"))
ggplot(df1,aes(x=rating,y=n,fill=div)) + geom_col() # plots in the order I want
ggplot(df1,aes(x=label,y=n,fill=div)) + geom_col() # doesn't because levels aren't sorted
How do I manage to copy the factor order from one column to another ?
I can make it work this way but I think it's really awkward:
lvls <- df1 %>% select(rating,label) %>% unique %>% arrange(rating) %>% extract2("label")
df1$label <- factor(df1$label,lvls)
ggplot(df1,aes(x=label,y=n,fill=div)) + geom_col()

Instead of adding a label column and use aes(x = label, you may stick to aes(x = rating, and create the labels in scale_x_discrete:
ggplot(df1, aes(x = rating, y = n, fill = div)) +
geom_col() +
scale_x_discrete(labels = df1 %>%
group_by(rating) %>%
summarize(n = sum(n)) %>%
mutate(lab = paste0(rating, " (", n, ")")) %>%
pull(lab))

Once you have set the levels of rating, you can use forcats to set the levels of label by the order of rating like this...
library(forcats)
df1 <- df1 %>% group_by(rating) %>%
mutate(label=paste0(rating," (",sum(n),")")) %>%
ungroup %>%
arrange(rating) %>% #sort by rating
mutate(label=fct_inorder(label)) #set levels by order in which they appear
Or you can use forcats::fct_reorder to do the same thing...
df1$label <- fct_reorder(df1$label, as.numeric(df1$rating))
The plot then has the bars in the right order.

Related

Shifting Values in R in rows

I have a problem that sounds easy, however, I could not find a solution in R. I would like to shift values according to the first year of the release. I mean the first column represents the years of the release and the columns are years when the device is broken (values are numbers of broken devices).
This is a solution in Python:
def f(x):
shifted = np.argmin((x.index.astype(int)< x.name[0]))
return x.shift(-shifted)
df = df.set_index(['Delivery Year', 'Freq']).apply(f, axis=1)
df.columns = [f'Year.{i + 1}' for i in range(len(df.columns))]
df = df.reset_index()
df
I would like to have it in R too.
# TEST
data <- data.frame(
`Delivery Year` = c('1976','1977','1978','1979'),
`Freq` = c(120,100,80,60),
`Year.1976` = c(10,NA,NA,NA),
`Year.1977` = c(5,3,NA,NA),
`Year.1978` = c(10,NA,8,NA),
`Year.1979` = c(13,10,5,14)
)
data
# DESIRED
data <- data.frame(
`Delivery Year` = c('1976','1977','1978','1979'),
`Freq` = c(120,100,80,60),
`Year.1` = c(10,3,8,14),
`Year.2` = c(5,NA,5,NA),
`Year.3` = c(10,10,NA,NA),
`Year.4` = c(13,NA,NA,NA)
)
data
In addition, would it be also possible to transform the number of broken devices into the percentage of Freq column?
Thank you
Using tidyverse
data %>%
pivot_longer(!c(Delivery.Year, Freq)) %>%
separate(name, c("Lab", "Year")) %>%
select(-Lab) %>%
mutate_all(as.numeric) %>%
filter(Year >= Delivery.Year) %>%
group_by(Delivery.Year, Freq) %>%
mutate(ind = paste0("Year.", row_number()),
per = value/Freq) %>%
ungroup() %>%
pivot_wider(id_cols = c(Delivery.Year, Freq), names_from = ind, values_from = c(value, per))
I pivoted it into long form to begin with and separated the original column names Year.1976, Year.1977, etc. to just get the years from the columns and dropped the Year piece of it. Then I converted all columns to numeric to allow for mathematical operations like filtering for when Year >= Delivery.Year. I then created a column to get the titles you did request Year.1, Year.2, etc. and calculated the percent. Then I pivot_wider to get it in the format you requested. One thing to note is that I was unclear if you wanted both the original values and the percent or just the percent. If you only want the percent then values_from = per should do it for you.
library(dplyr)
f <- function(df) {
years <- paste0("Year.",sort(as.vector(na.omit(as.integer(stringr::str_extract(colnames(df), "\\d+"))))))
df1 <- df %>% select(years)
df2 <- df %>% select(-years)
val <- c()
firstyear <- years[1]
for (k in 1:nrow(df1) ) {
vec <- as.numeric(as.vector(df1[k,]))
val[k] <- (as.numeric(suppressWarnings(na.omit(vec))))[1]
}
df1[firstyear] <- val
colnames(df1) <- c(paste0("Year.",seq(1:ncol(df1))))
df <- cbind(df2,df1)
print(df)
}
> f(data)
Delivery.Year Freq Year.1 Year.2 Year.3 Year.4
1 1976 120 10 5 10 13
2 1977 100 3 3 NA 10
3 1978 80 8 NA 8 5
4 1979 60 14 NA NA 14

Ggplot: how to show boxplots in a given order?

I have a peculiar problem with arranging boxplots given a certain order of the x-axis, as I am adding two boxplots from different dataframe in the same plot and each time I add the second geom_boxplot, R reorders my x axis alphabetically instead of following ordered levels of factor(x).
So, I have two dataframe of different lengths lookings something like this:
df1:
id value
1 A 1
2 A 2
3 A 3
4 A 5
5 B 10
6 B 8
7 B 1
8 C 3
9 C 7
df2:
id value
1 A 4
2 A 5
3 B 6
4 B 8
There is always more observations per id in df1 than in df2 and there is some ids in df1 that are not available in df2.
I'd like df1 to be sorted by the median(value) (ascending) and to first plot boxplots for each id in that order.
Then I add a second layer with boxplots for all other measurements per id from df2, which should maintain the same order on the x-axis.
Here's how I approached that:
vec <- df %>%
group_by(id) %>%
summarize(m = median(value)) %>%
arrange(m) %>%
pull(id)
p1 <- df1 %>%
ggplot(aes(x = factor(id, levels = vec), y = value)) +
geom_boxplot()
p1
p2 <- p1 +
geom_boxplot(data = df2, aes(x = factor(id, levels = vec), y = value))
p2
p1 shows the right order (ids are ordered based on ascending medians), p2 always throws my order off and goes back to plotting ids alphabetically (my id is a character column with names actually). I tried with sample dataframes and the above code achieves what is required. Hence, I am not sure what could be specifically wrong about my data so that the code fails when applied to the specific data and not the above mock data.
Any ideas?
Thanks a lot in advance!
If I understood correctly, this shoud work.
library(tidyverse)
# Sample data
df1 <-
tibble(
id = c("A","A","A","A","B","B","B","C","C"),
value = c(1,2,3,5,10,8,1,3,7),
type = "df1"
)
df2 <-
tibble(
id = c("A","A","B","B"),
value = c(4,5,6,8),
type = "df2"
)
df <-
# Create single data.frame
df1 %>%
bind_rows(df2) %>%
# Reorder id by median(value)
mutate(id = fct_reorder(id,value,median))
df %>%
ggplot(aes(id, y = value, fill = type)) +
geom_boxplot()

Plotting dummy variables with ggplot2

I actually need help building on this question:
ggplot2 graphic order by grouped variable instead of in alphabetical order.
I need to produce a similar graph and I actually have a problem with the black points. I have data where column names are dates and rows are filled with 0 or 1 and I need to plot the point if the value is 1. To reproduce, here is a small sample (in my dataset, there is over 300 columns):
df <- data.frame(id=c(1,2,3),
"26April1970"=c(0,0,1),
"14August1970"=c(0,1,0))
I need to plot the dates on the x axis, match the id to the canton and show the points where the value is 1.
Could anyone help?
Try this:
plot_data = df %>%
## put data in long format
pivot_longer(-id, names_to = "colname") %>%
## keep only 1s
filter(value == 1) %>%
## convert dates to Date class
mutate(date = as.Date(colname, format = "%d%B%Y"))
plot_data
# # A tibble: 2 x 4
# id colname value date
# <dbl> <chr> <dbl> <date>
# 1 2 14August1970 1 1970-08-14
# 2 3 26April1970 1 1970-04-26
## plot
ggplot(plot_data, aes(x = date, y = factor(id))) +
geom_point()
Using this data:
df <- data.frame(id=c(1,2,3),
"26April1970"=c(0,0,1),
"14August1970"=c(0,1,0), check.names = FALSE)
Maybe you are looking for this:
library(ggplot2)
library(dplyr)
library(tidyr)
#Data
df <- data.frame(id=c(1,2,3),
"26April1970"=c(0,0,1),
"14August1970"=c(0,1,0))
#Code
df %>% pivot_longer(-id) %>%
ggplot(aes(x=name,y=factor(value)))+
geom_point(aes(color=factor(value)))+
scale_color_manual(values=c('transparent','black'))+
theme(legend.position = 'none')+xlab('Date')+ylab('value')
Output:

How to plot "count" and "identity" in the same graph [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I have a list of decimal numbers, ranging from 1 to 40K and I am trying to plot a frequency histogram together with the total sum of a given bin. I'm attempting to do it using ggplot2 but getting lost on how to use the same x axis bins from the histogram:
sales <- data.frame(amount = runif(100, min=0, max=40000))
h <- hist(sales$amount, breaks=b)
sales$groups <- cut(sales$amount, breaks=h$breaks)
ggplot(sales,aes(x=groups)) +
geom_bar(stat="count")+
geom_bar(aes(x=groups, y=amount), stat="identity") +
scale_y_continuous(sec.axis = sec_axis(~.*5, name = "sum"))
I managed to create both graphs independently, but they seem to overwrite each other.
or
If I understand right, you tried to plot two different variables (Count and Sum) in the bar graph. As they have really different ranges, you need to define a secondary y axis.
First, the grammar of ggplot2 asks for one for column for x values, one column for y values and one or several columns for groups (I'm doing a very brief and dirty summary of my understanding of how ggplot2 works).
Here, the idea is to have your "breaks" as x variable, a second column with all y values to be plot and a group column stipulating if a y value belongs to the group "Count" or "amount". You can achieve this using dplyr and tidyr packages:
set.seed(123)
sales <- data.frame(amount = runif(100, min=0, max=40000))
b = 4
h <- hist(sales$amount, breaks=b)
sales$groups <- cut(sales$amount, breaks=h$breaks)
library(tidyr)
library(dplyr)
sales %>% group_by(groups) %>% mutate(Count = n()) %>%
pivot_longer(.,cols = c(Count, amount), names_to = "Variable", values_to = "Value")
# A tibble: 200 x 3
# Groups: groups [4]
groups Variable Value
<fct> <chr> <dbl>
1 (1e+04,2e+04] Count 27
2 (1e+04,2e+04] amount 11503.
3 (3e+04,4e+04] Count 27
4 (3e+04,4e+04] amount 31532.
5 (1e+04,2e+04] Count 27
6 (1e+04,2e+04] amount 16359.
7 (3e+04,4e+04] Count 27
8 (3e+04,4e+04] amount 35321.
9 (3e+04,4e+04] Count 27
10 (3e+04,4e+04] amount 37619.
# … with 190 more rows
However, if you are trying to plot this straight you will get a bad plot with bars for "Count" really small compared to "amount":
library(ggplot2)
library(tidyr)
library(dplyr)
sales %>% group_by(groups) %>% mutate(Count = n()) %>%
pivot_longer(.,cols = c(Count, amount), names_to = "Variable", values_to = "Value")%>%
ggplot(aes(x=groups, y = Value, fill = Variable)) +
geom_bar(stat="identity", position = position_dodge())
So, you can try to pass a secondary y axis using sec.axis argument in scale_y_continuous. However, this won't change your plot, it will simply create a "fake" right axis with the scale modify by the value you pass on the argument sec.axis:
So, if you want to have both group of values visible on your graph you need to either scale down "amount" or scale up "Count" in order that both group have a similar range of values.
Here, as you want to have the sum on the right axis, we will scale down the "Sum" in order it get values in the same range than "Count" values.
On the graph, you can see that "amount" values is reaching around 40000 whereas the maximal value of "Count" is 30. So, you can choose the following scale factor: 40000 / 30 = 1333.333.
So, now, if you create a second column called "Amount" that is the result of "amount" divided by 1300, you will have "Amount" and "Count" on the same range. So, your data will looks like that now:
library(dplyr)
library(tidyr)
sales %>% group_by(groups) %>% mutate(Count = n()) %>%
mutate(Amount = amount /1300) %>%
pivot_longer(.,cols = c(Count, Amount), names_to = "Variable", values_to = "Value")
# A tibble: 200 x 4
# Groups: groups [4]
amount groups Variable Value
<dbl> <fct> <chr> <dbl>
1 24000. (2e+04,3e+04] Count 30
2 24000. (2e+04,3e+04] Amount 18.5
3 13313. (1e+04,2e+04] Count 30
4 13313. (1e+04,2e+04] Amount 10.2
5 19545. (1e+04,2e+04] Count 30
6 19545. (1e+04,2e+04] Amount 15.0
7 38179. (3e+04,4e+04] Count 20
8 38179. (3e+04,4e+04] Amount 29.4
9 19316. (1e+04,2e+04] Count 30
10 19316. (1e+04,2e+04] Amount 14.9
# … with 190 more rows
In order the secondary y axis reflect the reality of "amount" values, you can pass the opposite scale factor and multiply it by 1300.
Altogether, you get the following code:
library(ggplot2)
library(dplyr)
library(tidyr)
sales %>% group_by(groups) %>% mutate(Count = n()) %>%
mutate(Amount = amount /1300) %>%
pivot_longer(.,cols = c(Count, Amount), names_to = "Variable", values_to = "Value") %>%
ggplot(aes(x=groups, y = Value, fill = Variable)) +
geom_bar(stat="identity", position = position_dodge()) +
scale_y_continuous(name = "Count",sec.axis = sec_axis(~.*1300, name = "Sum"))
Thus, you have the illusion to have plot two different group of values on two different scales.
Hope that this long explanation was helpful for you.

Boxplot dplyr: Error: non-numeric argument to binary operator

I have count the values of a column with dplyr.
yelp_tbl %>% select(name) %>% count(name)
The resulting data looks like this:
# A tibble: 108,999 x 2
name n
<chr> <int>
1 'do blow dry bar 1
2 'Round Table Tours 1
3 'S Hundehüttle 1
4 # 1 Nails 1
5 #1 Cochran Buick GMC of Monroeville 1
6 #1 Cochran Buick GMC of Robinson 1
7 #1 Cochran Cadillac - Monroeville 2
Now I want to make a boxplot of the "n" column.
yelp_tbl %>% select(name) %>% count(name) %>% boxplot(n)
But I got this result:
Error in x[floor(d)] + x[ceiling(d)] :
non-numeric argument to binary operator
Any Idea? Is it because of the function?
Pull the column out as a numeric vector and then do boxplot:
library(stringi)
df <- data.frame(name = stri_rand_strings(10000, 2, pattern = '[a-z]'))
df %>% select(name) %>% count(name) %>% pull(n) %>% boxplot()
# ^^^^^^
Try this (it's hard to know if it works without example data):
library(tidyverse)
yelp_tbl %>%
select(name) %>%
count(name) %>%
ggplot(aes(name, n)) +
geom_bar(stat = "identity", position = "dodge")
Try switching between x and y axis to see if it works the other way using boxplot(), or use a different function like ggplot() + geom_boxplot()
Example:
boxplot(yelp_tbl, aes(x = n, y = name))
or
yelp_tbl %>% ggplot(aes(n, name)) + geom_boxplot()

Resources