Matching columns in R and adding frequencies against them - r

I need to match my values in col1 with col 2 and col3 and if they match i need to add their frequencies.It should display the count from freq1 freq2 and freq3 of the unique values.
col1 freq1 col2 freq2 col3 freq3
apple 3 grapes 4 apple 1
grapes 5 apple 2 orange 2
orange 4 banana 5 grapes 2
guava 3 orange 6 banana 7
I need my output like this
apple 6
grapes 11
orange 12
guava 3
banana 12
I m a beginner.How do I code this in R.

We can use melt from data.table with patterns specified in the measure argument to convert the 'wide' format to 'long' format, then grouped by 'col', we get the sum of 'freq' column
library(data.table)
melt(setDT(df1), measure = patterns("^col", "^freq"),
value.name = c("col", "freq"))[,.(freq = sum(freq)) , by = col]
# col freq
#1: apple 6
#2: grapes 11
#3: orange 12
#4: guava 3
#5: banana 12
If it is alternating 'col', 'freq', columns, we can just unlist the subset of 'col' columns and 'freq' columns separately to create a data.frame (using c(TRUE, FALSE) to recycle for subsetting columns), and then use aggregate from base R to get the sum grouped by 'col'.
aggregate(freq~col, data.frame(col = unlist(df1[c(TRUE, FALSE)]),
freq = unlist(df1[c(FALSE, TRUE)])), sum)
# col freq
#1 apple 6
#2 banana 12
#3 grapes 11
#4 guava 3
#5 orange 12

I think that the easiest to understand for newbie would be creating 3 separate dataframes (I assumed here that your dataframe name is df):
df1 <- data.frame(df$col1, df$freq1)
colnames(df1) <- c("fruit", "freq")
df2 <- data.frame(df$col2, df$freq2)
colnames(df2) <- c("fruit", "freq")
df3 <- data.frame(df$col3, df$freq3)
colnames(df3) <- c("fruit", "freq")
Then bind all dataframes by rows:
df <- rbind(df1, df2, df3)
And at the end group by fruit and sum frequencies using dplyr library.
library(dplyr)
df <- df %>%
group_by(fruit)%>%
summarise(sum(freq))

Related

Transforming a list of lists into dataframe

I have a list containing a number of other lists, each of which contain varying numbers of character vectors, with varying numbers of elements. I want to create a dataframe where each list would be represented as a row and each character vector within that list would be a column. Where the character vector has > 1 element, the elements would be concatenated and separated using a "+" sign, so that they can be stored as one string. The data looks like this:
fruits <- list(
list(c("orange"), c("pear")),
list(c("pear", "orange")),
list(c("lemon", "apple"),
c("pear"),
c("grape"),
c("apple"))
)
The expected output is like this:
fruits_df <- data.frame(col1 = c("orange", "pear + orange", "lemon + apple"),
col2 = c("pear", NA, "pear"),
col3 = c(NA, NA, "grape"),
col4 = c(NA, NA, "apple"))
There is no limit on the number of character vectors that can be contained in a list, so the solution needs to dynamically create columns, leading to a df where the number of columns is equal to the length of the list containing the largest number of character vectors.
For every list in fruits you can create a one row dataframe and bind the data.
dplyr::bind_rows(lapply(fruits, function(x) as.data.frame(t(sapply(x,
function(y) paste0(y, collapse = "+"))))))
# V1 V2 V3 V4
#1 orange pear <NA> <NA>
#2 pear+orange <NA> <NA> <NA>
#3 lemon+apple pear grape apple
This is a bit messy but here is one way
cols <- lapply(fruits, function(x) sapply(x, paste, collapse=" + "))
ncols <- max(lengths(cols))
dd <- do.call("rbind.data.frame", lapply(cols, function(x) {length(x) <- ncols; x}))
names(dd) <- paste0("col", 1:ncol(dd))
dd
# col1 col2 col3 col4
# 1 orange pear <NA> <NA>
# 2 pear + orange <NA> <NA> <NA>
# 3 lemon + apple pear grape apple
or another strategy
ncols <- max(lengths(fruits))
dd <- data.frame(lapply(seq.int(ncols), function(x) sapply(fruits, function(y) paste(unlist(y[x]), collapse=" + "))))
names(dd) <- paste0("col", 1:ncols)
dd
But really you need to either build each column or row from your list and then combine them together.
Another approach that melts the list to a data.frame using rrapply::rrapply and then casts it to the required format using data.table::dcast:
library(rrapply)
library(data.table)
## melt to long data.frame
long <- rrapply(fruits, f = paste, how = "melt", collapse = " + ")
## cast to wide data.table
setDT(long)
dcast(long[, .(L1, L2, value = unlist(value))], L1 ~ L2)[, !"L1"]
#> ..1 ..2 ..3 ..4
#> 1: orange pear <NA> <NA>
#> 2: pear + orange <NA> <NA> <NA>
#> 3: lemon + apple pear grape apple

How to divide a dataframe column by 3 but skip division based on another column contents

I want to divide both numeric columns by 3 but not the third character column.
current dataframe:
col1 col2 col3
100 10 cat
200 20 dog
300 30 NA
desired:
col1 col2 col3
10 1 cat
20 2 dog
300 30 NA
my current code that isn't based on col3:
DB <- BD %>% mutate(Col1=Col1/3) %>% mutate(Col2s=Col2/3)
Please help with a solution. Thank you
Here is an idea via dplyr,
library(dplyr)
df %>%
mutate_at(vars(-3), list(~ifelse(!is.na(col3), ./10, .)))
# col1 col2 col3
#1 10 1 cat
#2 20 2 dog
#3 300 30 <NA>
Using base R.
no <- !is.na(dat$col3)
num <- sapply(dat, is.numeric)
dat[na, num] <- dat[na, num]/10
dat
# col1 col2 col3
# 1 10 1 cat
# 2 20 2 dog
# 3 300 30 <NA>
Data:
dat <- read.table(header=T, text="col1 col2 col3
100 10 cat
200 20 dog
300 30 NA")
try it this way
library(tidyverse)
df %>%
pivot_longer(-col3) %>%
mutate(value = ifelse(!is.na(col3), value / 3, value)) %>%
pivot_wider(col3, names_from = name, values_from = value)
In base R, we can directly do the assignment if we have a logical index
dat1[1:2][!is.na(dat1$col3),] <- dat1[1:2, !is.na(dat1$col3)]/10
Or using data.table
library(data.table)
setDT(dat1)[is.na(col3), (1:2) := .SD/10, .SDcols = 1:2]

R: Reshaping Multiple Columns from Long to Wide

Using following data:
library(tidyverse)
sample_df <- data.frame(Letter = c("a", "a", "a", "b", "b"),
Number = c(1,2,1,3,4),
Fruit = c("Apple", "Plum", "Peach", "Pear", "Peach"))
Letter Number Fruit
a 1 Apple
a 2 Plum
a 1 Peach
b 3 Pear
b 4 Peach
I want to transform a set of values from a long to a wide format:
Letter Number_1 Number_2 Fruit_1 Fruit_2 Fruit_3
a 1 2 Apple Plum Peach
b 3 4 Pear Peach
To do so, I unsuccessfully tried to create an index of each unique group combinations using c("Letter", "Number") and c("Letter", "Fruit"). Firstly, does this index need to be created, and if so how should it be done?
# Gets Unique Values, but no Index of Unique Combinations
sample_df1 <- sample_df %>%
group_by(Letter) %>%
mutate(Id1 = n_distinct(Letter, Number),
Id2 = n_distinct(Letter, Fruit))
# Gets Following Error: Column `Id1` must be length 3 (the group size) or one, not 2
sample_df1 <- sample_df %>%
group_by(Letter) %>%
mutate(Id1 = 1:n_distinct(Letter, Number),
Id2 = 1:n_distinct(Letter, Fruit))
# NOTE: Manually Created the Index Columns to show next problem
sample_df1 <- sample_df %>%
group_by(Letter) %>%
add_column(Id1 = c(1,2,1,1,2),
Id2 = c(1,2,3,1,2))
Assuming it did need to be done, I manually appended the desired values, and partially solved the problem using developmental tidyr.
# Requires Developmental Tidyr
devtools::install_github("tidyverse/tidyr")
sample_df1 %>%
pivot_wider(names_from = c("Id1", "Id2"), values_from = c("Number", "Fruit")) %>%
set_names(~ str_replace_all(.,"(\\w+.*)(_\\d)(_\\d)", "\\1\\3"))
# Letter Number_1 Number_2 Number_3 Fruit_1 Fruit_2 Fruit_3
#<fct> <dbl> <dbl> <dbl> <fct> <fct> <fct>
# a 1 2 1 Apple Plum Peach
# b 3 4 NA Pear Peach NA
However, this approach still created an unwanted Number_3 column. Using any tidyr, data.table or any other package, is there any way of getting the data in the desired format without duplicating columns?
An option would be to replace the duplicated elements by 'Letter' to NA and then in the reshaped data, remove the columns that are all NA
library(data.table)
out <- dcast(setDT(sample_df)[, lapply(.SD, function(x)
replace(x, duplicated(x), NA)), Letter], Letter ~ rowid(Letter),
value.var = c("Number", "Fruit"))
nm1 <- out[, names(which(!colSums(!is.na(.SD))))]
out[, (nm1) := NULL][]
# Letter Number_1 Number_2 Fruit_1 Fruit_2 Fruit_3
#1: a 1 2 Apple Plum Peach
#2: b 3 4 Pear Peach <NA>
If we want to use the tidyverse approach, a similar option can be used. Note that pivot_wider is from the dev version of tidyr (tidyr_0.8.3.9000)
library(tidyverse)
sample_df %>%
group_by(Letter) %>%
mutate_at(vars(-group_cols()), ~ replace(., duplicated(.), NA)) %>%
mutate(rn = row_number()) %>%
pivot_wider(
names_from = rn,
values_from = c("Number", "Fruit")) %>%
select_if(~ any(!is.na(.)))
# A tibble: 2 x 6
# Letter Number_1 Number_2 Fruit_1 Fruit_2 Fruit_3
# <fct> <dbl> <dbl> <fct> <fct> <fct>
#1 a 1 2 Apple Plum Peach
#2 b 3 4 Pear Peach <NA>

Put the combinations matrix of many rows in a column of a dataframe, then split it

I have a dataframe that looks like this (I simplify):
df <- data.frame(rbind(c(1, "dog", "cat", "rabbit"), c(2, "apple", "peach", "cucumber")))
colnames(df) <- c("ID", "V1", "V2", "V3")
## ID V1 V2 V3
## 1 1 dog cat rabbit
## 2 2 apple peach cucumber
I would like to create a column containing all possible combinations of variables V1:V3 two by two (order doesn't matter), but keeping a link with the original ID. So something like this.
## ID bigrams
## 1 1 dog cat
## 2 1 cat rabbit
## 3 1 dog rabbit
## 4 2 apple peach
## 5 2 apple cucumber
## 6 2 peach cucumber
My idea: use combn(), mutate() and separate_row().
library(tidyr)
library(dplyr)
df %>%
mutate(bigrams=paste(unlist(t(combn(df[,2:4],2))), collapse="-")) %>%
separate_rows(bigrams, sep="-") %>%
select(ID,bigrams)
The result is not what I expected... I guess that concatenating a matrix (the result of combine()) is not as easy as that.
I have two questions about this: 1) how to debug this code? 2) Is this a good way to do this kind of thing? I'm new on R but I’ve an Open Refine background, so concatenate-split multivalued cells make a lot of sense for me. But is this also the right method with R?
Thanks in advance for any help.
We can do this with data.table. Convert the 'data.frame' to 'data.table' (setDT(df)), melt it to 'long' format, grouped by 'ID', get the combn of 'value' and paste it together
library(data.table)
dM <- melt(setDT(df), id.var = "ID")[, combn(value, 2, FUN = paste, collapse=' '), ID]
setnames(dM, 2, 'bigrams')[]
# ID bigrams
#1: 1 dog cat
#2: 1 dog rabbit
#3: 1 cat rabbit
#4: 2 apple peach
#5: 2 apple cucumber
#6: 2 peach cucumber
I recommend #akrun's "melt first" approach, but just for fun, here are more ways to do it:
library(tidyverse)
df %>%
mutate_all(as.character) %>%
transmute(ID = ID, bigrams = pmap(
list(V1, V2, V3),
function(a, b, c) combn(c(a, b, c), 2, paste, collapse = " ")
))
# ID bigrams
# 1 1 dog cat, dog rabbit, cat rabbit
# 2 2 apple peach, apple cucumber, peach cucumber
(mutate_all(as.character) just because you gave us factors, and factor to character conversion can be surprising).
df %>%
mutate_all(as.character) %>%
nest(-ID) %>%
mutate(bigrams = map(data, combn, 2, paste, collapse = " ")) %>%
unnest(data) %>%
as.data.frame()
# ID bigrams V1 V2 V3
# 1 1 dog cat, dog rabbit, cat rabbit dog cat rabbit
# 2 2 apple peach, apple cucumber, peach cucumber apple peach cucumber
(as.data.frame() just for a prettier printing)

merge two data.frames and replace values of certain columns of df1 with values of df2

i have two data.frames that i want to merge and replace values of certain columns of df1
with values of df2. in this working example there are only 3 columns. but in the original data,
there are about 20 columns that should remain in the final data.frame.
NO <- c(2, 4, 7, 18, 25, 36, 48)
WORD <- c("apple", "peach", "plum", "orange", "grape", "berry", "pear")
CLASS <- c("p", "x", "x", "n", "x", "p", "n")
ColA <- c("hot", "warm", "sunny", "rainy", "windy", "cloudy", "snow")
df1 <- data.frame(NO, WORD, CLASS, ColA)
df1
# NO WORD CLASS ColA
# 1 2 apple p hot
# 2 4 peach x warm
# 3 7 plum x sunny
# 4 18 orange n rainy
# 5 25 grape x windy
# 6 36 berry p cloudy
# 7 48 pear n snow
NO <- c(4, 18, 36)
WORD <- c("patricia", "oliver", "bob")
CLASS <- c("p", "n", "x")
df2 <- data.frame(NO, WORD, CLASS)
df2
# NO WORD CLASS
# 1 4 patricia p
# 2 18 oliver n
# 3 36 bob x
i want to merge the two data.frames and replace the values of WORD and CLASS from df1
with the values of WORD and CLASS from df2
my data.frame should look like this:
# NO WORD CLASS ColA
# 1 2 apple p hot
# 2 4 patricia p warm
# 3 7 plum x sunny
# 4 18 oliver n rainy
# 5 25 grape x windy
# 6 36 bob x cloudy
# 7 48 pear n snow
Try this
auxind<-match(df2$NO, df1$NO) # Stores the repeated rows in df1
dfuni<-(rbind(df1[,1:3],df2)[-auxind,]) # Merges both data.frames and erases the repeated rows from the first three colums of df1
dfuni<-dfuni[order(dfuni$NO),] # Sorts the new data.frame
df1[,1:3]<-dfuni
This approach could work as well though is more playing around than the best answer to the question:
library(qdap); library(qdapTools)
df1[, 2] <- as.character(df1[, 2])
trms <- strsplit(df1[, 1] %lc% colpaste2df(df2, 2:3, keep.orig = FALSE), "\\.")
df1[sapply(trms, function(x) !all(is.na(x))), 2:3] <-
do.call(rbind, trms[sapply(trms, function(x) !all(is.na(x)))])

Resources