Related
I have a table with two columns A and B. I want to create a new table with two new columns added: X and Y.
The X column is to contain the values from the A column, but with the division performed. Values from the first row (from column A) divided by the values from the second row in column A and so for all subsequent rows, e.g. the third row divided by the fourth row etc.
The Y column is to contain the values from the B column, but with the division performed. Values from the first row (from column B) divided by the values from the second row in column B and so for all subsequent rows, e.g. the third row divided by the fourth row etc.
So far I used Excel for this. But now I need it in R if possible in the form of a function so that I can reuse this code easily. I haven't done this in R yet, so I am asking for help.
Example data:
structure(list(A = c(2L, 7L, 5L, 11L, 54L, 12L, 34L, 14L, 10L,
6L), B = c(3L, 5L, 1L, 21L, 67L, 32L, 19L, 24L, 44L, 37L)), class = "data.frame", row.names = c(NA,
-10L))
Sample results:
structure(list(A = c(2L, 7L, 5L, 11L, 54L, 12L, 34L, 14L, 10L,
6L), B = c(3L, 5L, 1L, 21L, 67L, 32L, 19L, 24L, 44L, 37L), X = c("",
"0,285714286", "", "0,454545455", "", "4,5", "", "2,428571429",
"", "1,666666667"), Y = c("", "0,6", "", "0,047619048", "", "2,09375",
"", "0,791666667", "", "1,189189189")), class = "data.frame", row.names = c(NA,
-10L))
You could use dplyr's across and lag (combined with modulo for picking every second row):
library(dplyr)
df |> mutate(across(c(A, B), ~ ifelse(row_number() %% 2 == 0, lag(.) / ., NA), .names = "new_{.col}"))
If you want a character vector change NA to "".
Output:
A B new_A new_B
1 2 3 NA NA
2 7 5 0.2857143 0.60000000
3 5 1 NA NA
4 11 21 0.4545455 0.04761905
5 54 67 NA NA
6 12 32 4.5000000 2.09375000
7 34 19 NA NA
8 14 24 2.4285714 0.79166667
9 10 44 NA NA
10 6 37 1.6666667 1.18918919
Function:
ab_fun <- function(data, vars) {
data |>
mutate(across(c(A, B), ~ ifelse(row_number() %% 2 == 0, lag(.) / ., NA), .names = "new_{.col}"))
}
ab_fun(df, c(A,B))
Updated with new data and correct code. + Function
I have a table with two columns A and B. I want to create a new table with two new columns added: X and Y. These two new columns are to contain data from column A, but every second row from column A. Correspondingly for column X, starting from the first value in column A and from the second value in column A for column Y.
So far, I have been doing it in Excel. But now I need it in R best function form so that I can easily reuse that code. I haven't done this in R yet, so I am asking for help.
Example data:
structure(list(A = c(2L, 7L, 5L, 11L, 54L, 12L, 34L, 14L, 10L,
6L), B = c(3L, 5L, 1L, 21L, 67L, 32L, 19L, 24L, 44L, 37L)), class = "data.frame", row.names = c(NA,
-10L))
Sample result:
structure(list(A = c(2L, 7L, 5L, 11L, 54L, 12L, 34L, 14L, 10L,
6L), B = c(3L, 5L, 1L, 21L, 67L, 32L, 19L, 24L, 44L, 37L), X = c(2L,
NA, 5L, NA, 54L, NA, 34L, NA, 10L, NA), Y = c(NA, 7L, NA, 11L,
NA, 12L, NA, 14L, NA, 6L)), class = "data.frame", row.names = c(NA,
-10L))
It is not a super elegant solution, but it works:
exampleDF <- structure(list(A = c(2L, 7L, 5L, 11L, 54L,
12L, 34L, 14L, 10L, 6L),
B = c(3L, 5L, 1L, 21L, 67L,
32L, 19L, 24L, 44L, 37L)),
class = "data.frame", row.names = c(NA, -10L))
index <- seq(from = 1, to = nrow(exampleDF), by = 2)
exampleDF$X <- NA
exampleDF$X[index] <- exampleDF$A[index]
exampleDF$Y <- exampleDF$A
exampleDF$Y[index] <- NA
You could also make use of the row numbers and the modulo operator:
A simple ifelse way:
library(dplyr)
df |>
mutate(X = ifelse(row_number() %% 2 == 1, A, NA),
Y = ifelse(row_number() %% 2 == 0, A, NA))
Or using pivoting:
library(dplyr)
library(tidyr)
df |>
mutate(name = ifelse(row_number() %% 2 == 1, "X", "Y"),
value = A) |>
pivot_wider()
A function using the first approach could look like:
See comment
xy_fun <- function(data, A = A, X = X, Y = Y) {
data |>
mutate({{X}} := ifelse(row_number() %% 2 == 1, {{A}}, NA),
{{Y}} := ifelse(row_number() %% 2 == 0, {{A}}, NA))
}
xy_fun(df, # Your data
A, # The col to take values from
X, # The column name of the first new column
Y # The column name of the second new column
)
Output:
A B X Y
1 2 3 2 NA
2 7 5 NA 7
3 5 1 5 NA
4 11 21 NA 11
5 54 67 54 NA
6 12 32 NA 12
7 34 19 34 NA
8 14 24 NA 14
9 10 44 10 NA
10 6 37 NA 6
Data stored as df:
df <- structure(list(A = c(2L, 7L, 5L, 11L, 54L, 12L, 34L, 14L, 10L, 6L),
B = c(3L, 5L, 1L, 21L, 67L, 32L, 19L, 24L, 44L, 37L)
),
class = "data.frame",
row.names = c(NA, -10L)
)
I like the #harre approach:
Another approach with base R we could ->
Use R's recycling ability (of a shorter-vector to a longer-vector):
df$X <- df$A
df$Y <- df$B
df$X[c(FALSE, TRUE)] <- NA
df$Y[c(TRUE, FALSE)] <- NA
df
A B X Y
1 2 3 2 NA
2 7 5 NA 5
3 5 1 5 NA
4 11 21 NA 21
5 54 67 54 NA
6 12 32 NA 32
7 34 19 34 NA
8 14 24 NA 24
9 10 44 10 NA
10 6 37 NA 37
Let's say I have this dataframe:
ID X1 X2
1 1 2
2 2 1
3 3 1
4 4 1
5 5 5
6 6 20
7 7 20
8 9 20
9 10 20
dataset <- structure(list(ID = 1:9, X1 = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 9L,
10L), X2 = c(2L, 1L, 1L, 1L, 5L, 20L, 20L, 20L, 20L)),
class = "data.frame", row.names = c(NA,
-9L))
And I want to select rows in which the absolute value of the subtraction of rows are more or equal to 2 (based on columns X1 and X2).
For example, row 4 value is 4-1, which is 3 and should be selected.
Row 9 value is 10-20, which is -10. Absolute value is 10 and should be selected.
In this case it would be rows 3, 4, 6, 7, 8 and 9
I tried:
dataset2 = dataset[,abs(dataset- c(dataset[,2])) > 2]
But I get an error.
The operation:
abs(dataset- c(dataset[,2])) > 2
Does give me rows that the sum are more than 2, but the result only works for my second column and does not select properly
We can get the difference between the 'X1', 'X2' columns, create a logical expression in subset to subset the rows
subset(dataset, abs(X1 - X2) >= 2)
# ID X1 X2
#3 3 3 1
#4 4 4 1
#6 6 6 20
#7 7 7 20
#8 8 9 20
#9 9 10 20
Or using index
subset(dataset, abs(dataset[[2]] - dataset[[3]]) >= 2)
data
dataset <- structure(list(ID = 1:9, X1 = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 9L,
10L), X2 = c(2L, 1L, 1L, 1L, 5L, 20L, 20L, 20L, 20L)),
class = "data.frame", row.names = c(NA,
-9L))
I have a matrix matrix with two level groupings as illustrated in the row and column names.
UKC1_SS1 UKC1_SS2 UKC2_SS1 UKC2_SS2
UKC1_SS1 1 2 3 4
UKC1_SS2 5 6 7 8
UKC2_SS1 9 10 11 12
UKC2_SS2 13 14 15 16
I want to create with a table with the column and row sums based on the first four digits of the column and row names:
UKC1 UKC2
UKC1 14 22
UKC2 46 54
I tried calculating rowsums and colSums sequentially,
sum.matrix <- rowsum(matrix, substr(rownames(matrix), start = 1, stop = 4))
sum.matrix <- colSums(sum.matrix, substr(colnames(test), start = 1, stop = 4)
but I receive the following error message:
Error in colSums(test, substr(colnames(test), start = 1, stop = 4)) :
invalid 'na.rm' argument
When I run sum(is.na) I confirm that there are NA values in matrix .
We can do the sum with xtabs after changing the dimnames with the substr of 1st 4 characters
dimnames(m1) <- lapply(dimnames(m1), substr, 1, 4)
xtabs(Freq~ Var1 + Var2, as.data.frame.table(m1))
# Var2
#Var1 UKC1 UKC2
# UKC1 14 22
# UKC2 46 54
data
m1 <- structure(c(1L, 5L, 9L, 13L, 2L, 6L, 10L, 14L, 3L, 7L, 11L, 15L,
4L, 8L, 12L, 16L), .Dim = c(4L, 4L), .Dimnames = list(c("UKC1_SS1",
"UKC1_SS2", "UKC2_SS1", "UKC2_SS2"), c("UKC1_SS1", "UKC1_SS2",
"UKC2_SS1", "UKC2_SS1.1")))
Start with the data:
> dput(Data1)
structure(list(X1 = structure(c(17L, 14L, 20L, 16L, 1L, 2L, 3L,
4L, 15L, 8L, 9L, 10L, 11L, 12L, 13L, 21L, 22L, 23L, 18L, 19L,
5L, 6L, 7L), .Label = c("Astra_1", "Astra_2", "Astra_3", "Astra_4",
"Audi_1", "Audi_2", "Audi_3", "BMW_1", "BMW_2", "BMW_3", "BMW_4",
"BMW_5", "Fiat_1", "Mazda_2", "Mercedes_1", "Nexia_1", "Porsche_1",
"Scania_1", "Scania_2", "Tico_1", "VW_1", "VW_2", "VW_3"), class = "factor"),
X2 = structure(c(2L, 3L, 10L, 7L, 8L, 12L, 9L, 14L, 11L,
4L, 5L, 6L, 15L, 13L, 4L, 5L, 9L, 14L, 11L, 1L, 3L, 10L,
16L), .Label = c("Astra_1", "Astra_3", "Astra_4", "Audi_1",
"Audi_2", "Audi_3", "BMW_1", "BMW_2", "Mazda_2", "Mercedes_1",
"Nexia_1", "Porsche_1", "Scania_2", "Tico_1", "VW_2", "VW_3"
), class = "factor"), AUC_1 = c(5860133.702, 1296009.939,
333123.4932, 250348.9407, 1376193.334, 4080502.863, 3777603.233,
3503973.487, 99101538.62, 231873.8462, 87258.75465, 147430.9913,
1028986.892, 1451482.832, 8136.72382, 25311.41683, 131352.7137,
565410.8186, 30196.23792, 70184.82268, 2526321.019, 381643.2138,
819687.9824), AUC_2 = c(4849720.322, 928980.4715, 320547.6185,
223287.2029, 1340641.323, 4720329.699, 4369150.434, 3371021.243,
108591253.3, 266489.7601, 85384.84604, 165726.7626, 1052130.559,
1470876.65, 9499.927679, 49309.74984, 138482.765, 444600.7911,
25132.73714, 55453.67019, 2038911.81, 422559.3293, 1445477.433
), ratio = c(1.20834467, 1.395088463, 1.03923247, 1.121196994,
1.02651866, 0.864452935, 0.864608186, 1.039439753, 0.91261069,
0.87010415, 1.021946618, 0.889602795, 0.978003046, 0.98681479,
0.856503765, 0.513314647, 0.948513078, 1.271726974, 1.201470327,
1.265647926, 1.2390536, 0.90317072, 0.567070757), Country = structure(c(1L,
1L, 2L, 3L, 5L, 1L, 5L, 1L, 4L, 7L, 4L, 7L, 7L, 7L, 6L, 6L,
6L, 6L, 8L, 8L, 6L, 6L, 7L), .Label = c("France", "Germany",
"Italy", "Norway", "Poland", "Spain", "Sweden", "Ukraine"
), class = "factor"), Comp = structure(c(3L, 5L, 16L, 9L,
8L, 9L, 12L, 14L, 4L, 15L, 11L, 14L, 16L, 17L, 10L, 10L,
12L, 13L, 1L, 2L, 5L, 6L, 7L), .Label = c("11,12", "12,13",
"12,13,14", "14,15", "14,15,16", "15,16,17", "16,17,18",
"2,3", "2,3,4", "3,4", "3,4,5", "4,5,6", "5,6", "5,6,7",
"5,6,7,8", "6,7,8", "7,8,9"), class = "factor")), .Names = c("X1",
"X2", "AUC_1", "AUC_2", "ratio", "Country", "Comp"), class = "data.frame", row.names = c(NA,
-23L))
Head of the data look like that:
X1 X2 AUC_1 AUC_2 ratio Country Comp
1 Porsche_1 Astra_3 5860133.7 4849720.3 1.2083447 France 12,13,14
2 Mazda_2 Astra_4 1296009.9 928980.5 1.3950885 France 14,15,16
3 Tico_1 Mercedes_1 333123.5 320547.6 1.0392325 Germany 6,7,8
4 Nexia_1 BMW_1 250348.9 223287.2 1.1211970 Italy 2,3,4
5 Astra_1 BMW_2 1376193.3 1340641.3 1.0265187 Poland 2,3
6 Astra_2 Porsche_1 4080502.9 4720329.7 0.8644529 France 2,3,4
Now we are going to focus on two last columns: Country and Comp. I would like to extract all rows which contains the same country and than compare if any of the numbers in column Comp is the same the strings from X1 and X2 should be stored together - possibly in the separate vectors or in the matrix. It's possible that one row may belong to different "clusters"/"vectors".
Example of desired output. That's just an example and the clustering is completly random. Any method for visualization of the output is acceptable.
Country 1 2 3 4 5 6
1 France Astra_3 Scania_2 Tico_1 NA NA NA
2 Poland Astra_4 Mazda_2 VW_3 Tico_2 NA NA
3 Sweden Mercedes_1 BMW_1 BMW_2 Audi_1 VW_3 NA
4 Norway BMW_1 Astra_1 Scania_2 Audi_3 NA NA
Assuming dat is your data.
library(data.table)
library(stringr)
setDT(dat)
dat[, `:=`(X1 = as.character(X1), X2 = as.character(X2),
Comp = str_split(as.character(Comp), ","))]
dat[, lapply(.SD, unlist), by = 1:nrow(dat)
][, .(X = paste(sort(unique(c(X1, X2))), collapse = ",")), by = .(Country, Comp)
][, .(SharedComp = paste(Comp, collapse = ",")), by = .(Country, X)] -> result
head(result)
Country X SharedComp
1: France Astra_3,Porsche_1 12,13
2: France Astra_3,Astra_4,Mazda_2,Porsche_1 14
3: France Astra_4,Mazda_2 15,16
4: Germany Mercedes_1,Tico_1 6,7,8
5: Italy BMW_1,Nexia_1 2,3,4
6: Poland Astra_1,BMW_2 2,3
If you want output to look more like in your question, it's necessary to do some reshaping.
dcast(result[, .(Country, SharedComp, X = str_split(X, ","))
][, lapply(.SD, unlist), by = 1:nrow(result)
][, i := seq_len(.N), by = nrow],
nrow + Country ~ i, value.var = "X")
nrow Country 1 2 3 4 5 6 7 8
1: 1 France Astra_3 Porsche_1 NA NA NA NA NA NA
2: 2 France Astra_3 Astra_4 Mazda_2 Porsche_1 NA NA NA NA
3: 3 France Astra_4 Mazda_2 NA NA NA NA NA NA
4: 4 Germany Mercedes_1 Tico_1 NA NA NA NA NA NA
5: 5 Italy BMW_1 Nexia_1 NA NA NA NA NA NA
6: 6 Poland Astra_1 BMW_2 NA NA NA NA NA NA
---
11: 11 Sweden Audi_1 Audi_3 BMW_1 BMW_3 NA NA NA NA
12: 12 Sweden Audi_1 Audi_3 BMW_1 BMW_3 BMW_4 VW_2 NA NA
13: 13 Sweden Audi_1 Audi_3 BMW_1 BMW_3 BMW_4 BMW_5 Scania_2 VW_2
---
25: 25 Spain Audi_2 Mercedes_1 NA NA NA NA NA NA
26: 26 Sweden Audi_3 VW_3 NA NA NA NA NA NA
nrow Country 1 2 3 4 5 6 7 8
I suppose what you want is: find all the rows with a given country, say Spain. Then within these rows, take all rows where a certain number occurs in the column Comp, e.g. 4. In these rows then extract the contents of columns X1 and X2 and put them together.
Maybe this code is what you want:
countries <- levels(data[,"Country"])
results <- list()
cn <- 1
for (i in 1:length(countries))
{
# find all row numbers with that country:
idx <- which(data[,"Country"] == countries[i])
# get all numbers which occur for that country:
numbers <- unique(as.numeric(unlist(strsplit(as.character(data[idx,"Comp"]), ","))))
for (j in 1:length(numbers))
{
# split all the numbers in the column "Comp" by ",":
CompList <- strsplit(as.character(data[idx,"Comp"]), ",")
# get all the row numbers for that country where numbers[j] is contained in the column "Comp":
rows <- idx[unlist(lapply(CompList, function(x) {any(x == as.character(numbers[j]))}))]
# assuming you want a number in the column "Comp" to occur at least in two rows:
if (length(rows) > 1)
{
results[[cn]] <- list("Country"= countries[i],
"Cars"= as.vector(as.matrix(data[rows, c("X1", "X2")])),
"ValueOfComp"=numbers[j])
cn <- cn + 1
}
}
}
This gives you something like this:
> results
[[1]]
[[1]]$Country
[1] "France"
[[1]]$Cars
[1] "Porsche_1" "Mazda_2" "Astra_3" "Astra_4"
[[1]]$ValueOfComp
[1] 14
[[2]]
[[2]]$Country
[1] "Spain"
[[2]]$Cars
[1] "Fiat_1" "VW_1" "Audi_1" "Audi_2"
[[2]]$ValueOfComp
[1] 3