diff --git a/DESCRIPTION b/DESCRIPTION index 8f33eea..ba02191 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: poorman Type: Package Title: A Poor Man's Dependency Free Recreation of 'dplyr' -Version: 0.2.5.6 +Version: 0.2.5.7 Authors@R: person("Nathan", "Eastwood", "", "nathan.eastwood@icloud.com", role = c("aut", "cre")) Maintainer: Nathan Eastwood @@ -18,7 +18,7 @@ Suggests: tinytest License: MIT + file LICENSE Encoding: UTF-8 -RoxygenNote: 7.1.1 +RoxygenNote: 7.2.0 Roxygen: list(markdown = TRUE) VignetteBuilder: knitr Language: en-GB diff --git a/NAMESPACE b/NAMESPACE index 777e9c6..a8a7e1d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -118,6 +118,8 @@ export(ntile) export(num_range) export(peek_vars) export(percent_rank) +export(pivot_longer) +export(pivot_wider) export(pull) export(recode) export(recode_factor) diff --git a/R/pivot_longer.R b/R/pivot_longer.R new file mode 100644 index 0000000..d5f0021 --- /dev/null +++ b/R/pivot_longer.R @@ -0,0 +1,138 @@ +#' Pivot data from wide to long +#' +#' `pivot_longer()` "lengthens" data, increasing the number of rows and decreasing the number of columns. The inverse +#' transformation is [pivot_wider()]. +#' +#' @param data `data.frame`. The data to pivot. +#' @param cols <[`poor-select`][select_helpers]>. Columns to pivot into longer format. +#' @param names_to `character(n)`. The name of the new column(s) that will contain the column names. +#' @param names_prefix `character(1)`. A regular expression used to remove matching text from the start of each variable +#' name. +#' @param names_sep,names_pattern `character(1)`. If `names_to` contains multiple values, this argument controls how the +#' column name is broken up. `names_pattern` takes a regular expression containing matching groups (`()`⁠). +#' @param values_to `character(n)`. The name of the new column(s) that will contain the values of the pivoted variables. +#' @param values_drop_na `logical(1)`. If `TRUE`, will drop rows that contain only `NA` in the `values_to` column. This +#' effectively converts explicit missing values to implicit missing values, and should generally be used only when +#' missing values in data were created by its structure. +#' @param ... Additional arguments passed on to methods. +#' +#' @return A `data.frame`. +#' +#' @examples +#' wide_data <- data.frame(replicate(5, rnorm(10))) +#' # Customizing the names +#' pivot_longer( +#' data = wide_data, +#' cols = c(1, 2), +#' names_to = "Column", +#' values_to = "Numbers" +#' ) +#' +#' @export +pivot_longer <- function( + data, + cols, + names_to = "name", + names_prefix = NULL, + names_sep = NULL, + names_pattern = NULL, + values_to = "value", + values_drop_na = FALSE, + ... +) { + + if (missing(cols)) { + stop("`cols` must select at least one column.") + } + + cols <- names(eval_select_pos(data, substitute(cols))) + + if (any(names_to %in% setdiff(names(data), cols))) { + stop( + paste0( + "Some values of the columns specified in 'names_to' are already present + as column names. Either use another value in `names_to` or rename the + following columns: ", + paste(names_to[which(names_to %in% setdiff(names(data), cols))], sep = ", ") + ), + call. = FALSE) + } + + # Sanity checks ---------------- + + # nothing to select? + if (length(cols) == 0L) { + stop("No columns found for reshaping data.", call. = FALSE) + } + + # Reshaping --------------------- + # Create Index column as needed by reshape + data[["_Row"]] <- as.numeric(rownames(data)) + + # Create a new index for cases with length(names_to) > 1 + names_to_2 <- paste(names_to, collapse = "_") + + # Reshape + long <- stats::reshape( + data, + varying = cols, + idvar = "_Row", + v.names = values_to, + timevar = names_to_2, + direction = "long" + ) + + # Cleaning -------------------------- + # Sort the dataframe (to match pivot_longer's output) + long <- long[do.call(order, long[, c("_Row", names_to_2)]), ] + + long[["_Row"]] <- NULL + + # Re-insert col names as levels + long[[names_to_2]] <- cols[long[[names_to_2]]] + + # if several variable in names_to, split the names either with names_sep or with names_pattern + if (length(names_to) > 1) { + for (i in seq_along(names_to)) { + if (is.null(names_pattern)) { + new_vals <- unlist(lapply( + strsplit(unique(long[[names_to_2]]), names_sep, fixed = TRUE), + function(x) x[i] + )) + long[[names_to[i]]] <- new_vals + } else { + colPattern <- regmatches( + x = unique(long[[names_to_2]]), + m = regexec(names_pattern, unique(long[[names_to_2]])) + ) + colPattern <- as.data.frame(do.call(rbind, colPattern))[, c(1, i + 1)] + names(colPattern) <- c(names_to_2, names_to[i]) + long <- left_join(x = long, y = colPattern, by = names_to_2) + } + } + long[[names_to_2]] <- NULL + } + + # reorder + long <- relocate(.data = long, values_to, .after = -1) + + # remove names prefix if specified + if (!is.null(names_prefix)) { + if (length(names_to) > 1) { + stop("`names_prefix` only works when `names_to` is of length 1.", call. = FALSE) + } + long[[names_to]] <- gsub(paste0("^", names_prefix), "", long[[names_to]]) + } + + if (values_drop_na) { + long <- long[!is.na(long[, values_to]), ] + } + + # Reset row names + rownames(long) <- NULL + + # Remove reshape attributes + attributes(long)$reshapeLong <- NULL + + long +} diff --git a/R/pivot_wider.R b/R/pivot_wider.R new file mode 100644 index 0000000..c1dbe59 --- /dev/null +++ b/R/pivot_wider.R @@ -0,0 +1,217 @@ +#' Pivot data from long to wide +#' +#' `pivot_wider()` "widens" data, increasing the number of columns and decreasing the number of rows. The inverse +#' transformation is [pivot_longer()]. +#' +#' @param data `data.frame`. The data to pivot. +#' @param id_cols `character(1)`. The name of the column that identifies the rows. If `NULL`, it will use all the unique +#' rows. +#' @param names_from `character(n)`. The name of the column(s) that contains the levels to be used as future column +#' names. +#' @param names_prefix `character(1)`. String added to the start of every variable name. This is particularly useful if +#' `names_from` is a numeric vector and you want to create syntactic variable names. +#' @param names_sep `character(1)`. If `names_from` or `values_from` contains multiple variables, this will be used to +#' join their values together into a single string to use as a column name. +#' @param names_glue `character(1)`. Instead of `names_sep` and `names_prefix`, you can supply a +#' [glue specification](https://glue.tidyverse.org/index.html) that uses the `names_from` columns to create custom +#' column names. Note that the only delimiters supported by `names_glue` are curly brackets, `{` and `}`. +#' @param values_from `character(n)`. The name of the column that contains the values to be used as future variable +#' values. +#' @param values_fill `numeric(n)`. Optionally, a (scalar) value that will be used to replace missing values in the new +#' columns created. +#' @param ... Not used for now. +#' +#' @return If a tibble was provided as input, `pivot_wider()` also returns a +#' tibble. Otherwise, it returns a data frame. +#' +#' @examples +#' data_long <- read.table(header = TRUE, text = " +#' subject sex condition measurement +#' 1 M control 7.9 +#' 1 M cond1 12.3 +#' 1 M cond2 10.7 +#' 2 F control 6.3 +#' 2 F cond1 10.6 +#' 2 F cond2 11.1 +#' 3 F control 9.5 +#' 3 F cond1 13.1 +#' 3 F cond2 13.8 +#' 4 M control 11.5 +#' 4 M cond1 13.4 +#' 4 M cond2 12.9") +#' +#' +#' pivot_wider( +#' data_long, +#' id_cols = "subject", +#' names_from = "condition", +#' values_from = "measurement" +#' ) +#' +#' pivot_wider( +#' data_long, +#' id_cols = "subject", +#' names_from = "condition", +#' values_from = "measurement", +#' names_prefix = "Var.", +#' names_sep = "." +#' ) +#' +#' production <- expand.grid( +#' product = c("A", "B"), +#' country = c("AI", "EI"), +#' year = 2000:2014 +#' ) +#' production <- filter(production, (product == "A" & country == "AI") | product == "B") +#' +#' production$production <- rnorm(nrow(production)) +#' +#' pivot_wider( +#' production, +#' names_from = c("product", "country"), +#' values_from = "production", +#' names_glue = "prod_{product}_{country}" +#' ) +#' +#' @export +pivot_wider <- function( + data, + id_cols = NULL, + values_from = "Value", + names_from = "Name", + names_sep = "_", + names_prefix = "", + names_glue = NULL, + values_fill = NULL, + ... +) { + + old_names <- names(data) + + # Preserve attributes + variable_attr <- lapply(data, attributes) + + # Create an id for stats::reshape + if (is.null(id_cols)) { + data[["_Rows"]] <- apply( + X = data[, !names(data) %in% c(values_from, names_from), drop = FALSE], + MARGIN = 1, + FUN = paste, + collapse = "_" + ) + id_cols <- "_Rows" + } + + # create pattern of column names - stats::reshape renames columns that concatenates "v.names" + values - we only want + # values + current_colnames <- colnames(data) + current_colnames <- current_colnames[current_colnames != "_Rows"] + if (is.null(names_glue)) { + future_colnames <- unique(apply(data, 1, function(x) paste(x[c(names_from)], collapse = names_sep))) + } else { + vars <- regmatches(names_glue, gregexpr("\\{\\K[^{}]+(?=\\})", names_glue, perl = TRUE))[[1]] + tmp_data <- unique(data[, vars]) + future_colnames <- unique(apply(tmp_data, 1, function(x) { + tmp_vars <- list() + for (i in seq_along(vars)) { + tmp_vars[[i]] <- x[vars[i]] + } + + tmp_colname <- gsub("\\{\\K[^{}]+(?=\\})", "", names_glue, perl = TRUE) + tmp_colname <- gsub("\\{\\}", "%s", tmp_colname) + do.call(sprintf, c(fmt = tmp_colname, tmp_vars)) + })) + } + + # stop if some column names would be duplicated (follow tidyr workflow) + if (any(future_colnames %in% current_colnames)) { + stop( + paste0( + "Some values of the columns specified in 'names_from' are already present + as column names. Either use `name_prefix` or rename the following columns: ", + paste(current_colnames[which(current_colnames %in% future_colnames)], sep = ", ") + ), + call. = FALSE + ) + } + + # stats::reshape works strangely when several variables are in idvar/timevar so we unite all ids in a single temporary + # column that will be used by stats::reshape + data$new_time <- apply(data, 1, function(x) paste(x[names_from], collapse = "_")) + data[, names_from] <- NULL + + wide <- stats::reshape( + data, + v.names = values_from, + idvar = id_cols, + timevar = "new_time", + sep = names_sep, + direction = "wide" + ) + + # Clean + if ("_Rows" %in% names(wide)) wide[["_Rows"]] <- NULL + rownames(wide) <- NULL + + if (length(values_from) == 1) { + to_rename <- which(startsWith(names(wide), paste0(values_from, names_sep))) + names(wide)[to_rename] <- future_colnames + } + + # Order columns as in tidyr + if (length(values_from) > 1) { + for (i in values_from) { + tmp1 <- wide[, which(!startsWith(names(wide), i))] + tmp2 <- wide[, which(startsWith(names(wide), i))] + wide <- cbind(tmp1, tmp2) + # doesn't work + # wide <- relocate(wide, starts_with(i), .after = -1) + } + } + + new_cols <- setdiff(names(wide), old_names) + names(wide)[which(names(wide) %in% new_cols)] <- paste0(names_prefix, new_cols) + + # Fill missing values + if (!is.null(values_fill)) { + if (length(values_fill) == 1) { + if (is.numeric(wide[[new_cols[1]]])) { + if (!is.numeric(values_fill)) { + stop(paste0("`values_fill` must be of type numeric."), call. = FALSE) + } else { + for (i in new_cols) { + wide[[i]] <- replace_na(wide[[i]], replace = values_fill) + } + } + } else if (is.character(wide[[new_cols[1]]])) { + if (!is.character(values_fill)) { + stop(paste0("`values_fill` must be of type character."), call. = FALSE) + } else { + for (i in new_cols) { + wide[[i]] <- replace_na(wide[[i]], replace = values_fill) + } + } + } else if (is.factor(wide[[new_cols[1]]])) { + if (!is.factor(values_fill)) { + stop(paste0("`values_fill` must be of type factor."), call. = FALSE) + } else { + for (i in new_cols) { + wide[[i]] <- replace_na(wide[[i]], replace = values_fill) + } + } + } + } else { + stop("`values_fill` must be of length 1.", call. = FALSE) + } + } + + # Remove reshape attributes + attributes(wide)$reshapeWide <- NULL + + # add back attributes where possible + for (i in colnames(wide)) { + attributes(wide[[i]]) <- variable_attr[[i]] + } + + wide +} diff --git a/inst/tinytest/test_pivot_longer.R b/inst/tinytest/test_pivot_longer.R new file mode 100644 index 0000000..359ab3f --- /dev/null +++ b/inst/tinytest/test_pivot_longer.R @@ -0,0 +1,56 @@ +state_x77 <- as.data.frame(state.x77) %>% rownames_to_column(var = "State") + +res <- state_x77 %>% + pivot_longer(cols = -State, names_to = "Statistic", values_to = "Value") + +expect_equal(dim(res), c(400, 3)) +expect_equal(colnames(res), c("State", "Statistic", "Value")) + +df <- data.frame(id = c(1, 2), v1.act = c(0.4, 0.8), v2.act = c(0.5, 0.7), v1.fix = c(1, 0), v2.fix = c(0, 1)) +res <- pivot_longer(df, -id, names_to = c("var", "type"), names_pattern = "v(.).(.*)") +expect_equal(dim(res), c(8, 4)) +expect_equal(colnames(res), c("id", "var", "type", "value")) + +res <- pivot_longer(df, -id, names_to = c("var", "type"), names_sep = ".") +expect_equal(dim(res), c(8, 4)) +expect_equal(colnames(res), c("id", "var", "type", "value")) + +df <- data.frame(x = 1:2, y = 3:4) +pv <- pivot_longer(df, x:y) +expect_equal(names(pv), c("name", "value")) +expect_equal(pv$name, rep(names(df), 2)) +expect_equal(pv$value, c(1, 3, 2, 4)) + + +df <- data.frame( + x = c(1, 2), + y = c(10, 20), + z = c(100, 200) +) +pv <- pivot_longer(df, 1:3) +expect_equal(pv$value, c(1, 10, 100, 2, 20, 200)) + + +df <- data.frame(x = 1:2, y = 2, z = 1:2) +pv <- pivot_longer(df, y:z) +expect_equal(names(pv), c("x", "name", "value")) +expect_equal(pv$x, rep(df$x, each = 2)) + + +df <- data.frame(x = c(1, NA), y = c(NA, 2)) +pv <- pivot_longer(df, x:y, values_drop_na = TRUE) +expect_equal(pv$name, c("x", "y")) +expect_equal(pv$value, c(1, 2)) + + +df <- data.frame(x = factor("a"), y = factor("b")) +pv <- pivot_longer(df, x:y) +expect_equal(pv$value, factor(c("a", "b"))) + + +df <- data.frame(x = 1, y = 2) + +expect_error( + pivot_longer(df, y, names_to = "x"), + pattern = "Some values of the columns specified in 'names_to' are already present" +) diff --git a/inst/tinytest/test_pivot_wider.R b/inst/tinytest/test_pivot_wider.R new file mode 100644 index 0000000..4e5426a --- /dev/null +++ b/inst/tinytest/test_pivot_wider.R @@ -0,0 +1,135 @@ +df <- data.frame(key = c("x", "y", "z"), val = 1:3) +pv <- pivot_wider(df, names_from = "key", values_from = "val") + +expect_equal(names(pv), c("x", "y", "z")) +expect_equal(nrow(pv), 1) + +df <- data.frame(a = 1, key = c("x", "y"), val = 1:2) +pv <- pivot_wider(df, names_from = "key", values_from = "val") + +expect_equal(names(pv), c("a", "x", "y")) +expect_equal(nrow(pv), 1) + +df <- data.frame(a = 1:2, key = c("x", "y"), val = 1:2) +pv <- pivot_wider(df, names_from = "key", values_from = "val") + +expect_equal(pv$a, c(1, 2)) +expect_equal(pv$x, c(1, NA)) +expect_equal(pv$y, c(NA, 2)) + +df <- data.frame( + a = c(1, 1), + key = c("a", "b"), + val = c(1, 2) +) + +expect_error( + pivot_wider(df, names_from = "key", values_from = "val"), + pattern = "Some values of the columns specified" +) + +df <- data.frame( + variable = c("a", "b", "c"), + date = c("2015-01-01", "2015-01-01", "2015-01-02"), + value = c(1, 2, 3), + stringsAsFactors = FALSE +) + +res <- df %>% pivot_wider( + names_from = "date", + values_from = "value", + values_fill = 0 +) + +expect_equal( + res, + structure( + list(variable = c("a", "b", "c"), `2015-01-01` = c(1, 2, 0), `2015-01-02` = c(0, 0, 3)), + row.names = c(NA, 3L), + class = "data.frame" + ) +) + + +production <- expand.grid( + product = c("A", "B"), + country = c("AI", "EI"), + year = 2000:2014 +) %>% + filter((product == "A" & country == "AI") | product == "B") + +production$production <- rnorm(nrow(production)) + +res <- production %>% + pivot_wider( + names_from = c("product", "country"), + values_from = "production" + ) +expect_equal(dim(res), c(15, 4)) +expect_equal(colnames(res), c("year", "A_AI", "B_AI", "B_EI")) + +res <- production %>% + pivot_wider( + names_from = c("product", "country"), + values_from = "production", + names_glue = "prod_{product}_{country}" + ) +expect_equal(colnames(res), c("year", "prod_A_AI", "prod_B_AI", "prod_B_EI")) + +res <- production %>% + pivot_wider( + names_from = c("product", "country"), + values_from = "production", + names_sep = "." + ) + +expect_equal(colnames(res), c("year", "A.AI", "B.AI", "B.EI")) + +contacts <- data.frame( + field = c("name", "company", "name", "company", "email", "name"), + value = c("Jiena McLellan", "Toyota", "John Smith", "google", "john@google.com", "Huxley Ratcliffe") +) +contacts$person_id <- cumsum(contacts$field == "name") + +res <- contacts %>% + pivot_wider(names_from = "field", values_from = "value") + +expect_equal( + res, + structure( + list( + person_id = 1:3, + name = c("Jiena McLellan", "John Smith", "Huxley Ratcliffe"), + company = c("Toyota", "google", NA), + email = c(NA, "john@google.com", NA) + ), + row.names = c(NA, 3L), + class = "data.frame" + ) +) + +df <- data.frame( + food = c("banana", "banana", "banana", "banana", "cheese", "cheese", "cheese", "cheese"), + binary = c(rep(c("yes", "no"), 4)), + car = c("toyota", "subaru", "mazda", "skoda", "toyota", "subaru", "mazda", "skoda"), + fun = c(2, 4, 3, 6, 2, 4, 2, 3) +) + +res <- df %>% + pivot_wider( + id_cols = "food", + names_from = c("car", "binary"), + names_glue = "{binary}_{car}", + values_from = "fun" + ) + +expect_equal( + res, + structure( + list( + food = c("banana", "cheese"), yes_toyota = c(2, 2), no_subaru = c(4, 4), yes_mazda = c(3, 2), no_skoda = c(6, 3) + ), + row.names = 1:2, + class = "data.frame" + ) +) diff --git a/man/pivot_longer.Rd b/man/pivot_longer.Rd new file mode 100644 index 0000000..4c42233 --- /dev/null +++ b/man/pivot_longer.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/pivot_longer.R +\name{pivot_longer} +\alias{pivot_longer} +\title{Pivot data from wide to long} +\usage{ +pivot_longer( + data, + cols, + names_to = "name", + names_prefix = NULL, + names_sep = NULL, + names_pattern = NULL, + values_to = "value", + values_drop_na = FALSE, + ... +) +} +\arguments{ +\item{data}{\code{data.frame}. The data to pivot.} + +\item{cols}{<\code{\link[=select_helpers]{poor-select}}>. Columns to pivot into longer format.} + +\item{names_to}{\code{character(n)}. The name of the new column(s) that will contain the column names.} + +\item{names_prefix}{\code{character(1)}. A regular expression used to remove matching text from the start of each variable +name.} + +\item{names_sep, names_pattern}{\code{character(1)}. If \code{names_to} contains multiple values, this argument controls how the +column name is broken up. \code{names_pattern} takes a regular expression containing matching groups (\verb{()}⁠).} + +\item{values_to}{\code{character(n)}. The name of the new column(s) that will contain the values of the pivoted variables.} + +\item{values_drop_na}{\code{logical(1)}. If \code{TRUE}, will drop rows that contain only \code{NA} in the \code{values_to} column. This +effectively converts explicit missing values to implicit missing values, and should generally be used only when +missing values in data were created by its structure.} + +\item{...}{Additional arguments passed on to methods.} +} +\value{ +A \code{data.frame}. +} +\description{ +\code{pivot_longer()} "lengthens" data, increasing the number of rows and decreasing the number of columns. The inverse +transformation is \code{\link[=pivot_wider]{pivot_wider()}}. +} +\examples{ +wide_data <- data.frame(replicate(5, rnorm(10))) +# Customizing the names +pivot_longer( + data = wide_data, + cols = c(1, 2), + names_to = "Column", + values_to = "Numbers" +) + +} diff --git a/man/pivot_wider.Rd b/man/pivot_wider.Rd new file mode 100644 index 0000000..555374c --- /dev/null +++ b/man/pivot_wider.Rd @@ -0,0 +1,103 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/pivot_wider.R +\name{pivot_wider} +\alias{pivot_wider} +\title{Pivot data from long to wide} +\usage{ +pivot_wider( + data, + id_cols = NULL, + values_from = "Value", + names_from = "Name", + names_sep = "_", + names_prefix = "", + names_glue = NULL, + values_fill = NULL, + ... +) +} +\arguments{ +\item{data}{\code{data.frame}. The data to pivot.} + +\item{id_cols}{\code{character(1)}. The name of the column that identifies the rows. If \code{NULL}, it will use all the unique +rows.} + +\item{values_from}{\code{character(n)}. The name of the column that contains the values to be used as future variable +values.} + +\item{names_from}{\code{character(n)}. The name of the column(s) that contains the levels to be used as future column +names.} + +\item{names_sep}{\code{character(1)}. If \code{names_from} or \code{values_from} contains multiple variables, this will be used to +join their values together into a single string to use as a column name.} + +\item{names_prefix}{\code{character(1)}. String added to the start of every variable name. This is particularly useful if +\code{names_from} is a numeric vector and you want to create syntactic variable names.} + +\item{names_glue}{\code{character(1)}. Instead of \code{names_sep} and \code{names_prefix}, you can supply a +\href{https://glue.tidyverse.org/index.html}{glue specification} that uses the \code{names_from} columns to create custom +column names. Note that the only delimiters supported by \code{names_glue} are curly brackets, \verb{\{} and \verb{\}}.} + +\item{values_fill}{\code{numeric(n)}. Optionally, a (scalar) value that will be used to replace missing values in the new +columns created.} + +\item{...}{Not used for now.} +} +\value{ +If a tibble was provided as input, \code{pivot_wider()} also returns a +tibble. Otherwise, it returns a data frame. +} +\description{ +\code{pivot_wider()} "widens" data, increasing the number of columns and decreasing the number of rows. The inverse +transformation is \code{\link[=pivot_longer]{pivot_longer()}}. +} +\examples{ +data_long <- read.table(header = TRUE, text = " + subject sex condition measurement + 1 M control 7.9 + 1 M cond1 12.3 + 1 M cond2 10.7 + 2 F control 6.3 + 2 F cond1 10.6 + 2 F cond2 11.1 + 3 F control 9.5 + 3 F cond1 13.1 + 3 F cond2 13.8 + 4 M control 11.5 + 4 M cond1 13.4 + 4 M cond2 12.9") + + +pivot_wider( + data_long, + id_cols = "subject", + names_from = "condition", + values_from = "measurement" +) + +pivot_wider( + data_long, + id_cols = "subject", + names_from = "condition", + values_from = "measurement", + names_prefix = "Var.", + names_sep = "." +) + +production <- expand.grid( + product = c("A", "B"), + country = c("AI", "EI"), + year = 2000:2014 +) +production <- filter(production, (product == "A" & country == "AI") | product == "B") + +production$production <- rnorm(nrow(production)) + +pivot_wider( + production, + names_from = c("product", "country"), + values_from = "production", + names_glue = "prod_{product}_{country}" +) + +}