|
| 1 | +--- |
| 2 | +pre: <b>2/15. </b> |
| 3 | +title: "List and Apply" |
| 4 | +weight: 9 |
| 5 | +summary: "Learn the apply family of functions." |
| 6 | +format: |
| 7 | + gfm: |
| 8 | + toc: true |
| 9 | + output-file: "_index.en.md" |
| 10 | + reference-links: true |
| 11 | + code-link: true |
| 12 | +editor_options: |
| 13 | + chunk_output_type: console |
| 14 | +--- |
| 15 | + |
| 16 | +```{r, include=FALSE} |
| 17 | +answers = FALSE |
| 18 | +``` |
| 19 | + |
| 20 | +## Overview |
| 21 | + |
| 22 | +The apply family of functions can be a handy way to perform analyses and transformations of data quickly. Today we will be using both to improve some previous tasks. First we will be revisiting `comma_split()` (for the last time), and solving an old annoyance regarding reading in several data files. |
| 23 | + |
| 24 | +## The Data |
| 25 | + |
| 26 | +We are going to be using class survey data for lab today. Please load it in using the following: |
| 27 | + |
| 28 | +```{r} |
| 29 | +survey = read.csv("https://raw.githubusercontent.com/Intro-to-Data-Science-Template/intro_to_data_science_reader/main/content/class_worksheets/4_r_rstudio/data/survey_data.csv") |
| 30 | +``` |
| 31 | + |
| 32 | +## Apply `comma_split()` |
| 33 | + |
| 34 | +The first thing we will be doing today is combining our `pet_split()` function and `lapply()` in order to split all of our `<DRINK>_day` comma separated columns at once. I've provided our fully generalized version of `pet_split()` below. Run the following to add it to your environment. |
| 35 | + |
| 36 | +```{r} |
| 37 | +pet_split = function(pet_vector, possible_columns){ |
| 38 | + |
| 39 | + # make a base dataframe with rows for each of our cases. |
| 40 | + pet_output = data.frame( |
| 41 | + "id" = 1:length(pet_vector) |
| 42 | + ) |
| 43 | + |
| 44 | + # iterate through all options and create a column with NAs for it |
| 45 | + for(option in possible_columns){ |
| 46 | + |
| 47 | + # make a new column with a character version of each possible option. |
| 48 | + pet_output[, as.character(option)] = NA |
| 49 | + |
| 50 | + } |
| 51 | + |
| 52 | + # fill output df |
| 53 | + for(option in possible_columns){ |
| 54 | + |
| 55 | + # fill dataframe iterativly. |
| 56 | + pet_output[ , option] = grepl(option, pet_vector, ignore.case = TRUE) |
| 57 | + |
| 58 | + } |
| 59 | + |
| 60 | + # clear all know options |
| 61 | + for(option in possible_columns){ |
| 62 | + |
| 63 | + # remove all known options |
| 64 | + pet_vector = gsub(pattern = option, pet_vector, replacement = '', ignore.case = TRUE) |
| 65 | + |
| 66 | + } |
| 67 | + |
| 68 | + # clear commas and whitespace |
| 69 | + pet_vector = gsub(pattern = ',', pet_vector, replacement = '', ignore.case = TRUE) |
| 70 | + pet_vector = trimws(pet_vector) |
| 71 | + |
| 72 | + # Fill in 'other' |
| 73 | + pet_output$other = pet_vector |
| 74 | + # Turn blanks into NAs |
| 75 | + pet_output[pet_output$other == "" & !is.na(pet_output$other), 'other'] = NA |
| 76 | + |
| 77 | + # return output |
| 78 | + return(pet_output) |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +Once you've got the function in your environment, we are going to use `lapply()` to apply it over all the relevant columns in our `survey` dataframe. |
| 83 | + |
| 84 | +::: {.question} |
| 85 | +Use `lapply()` to apply `pet_split()` to all of the `<DRINK>_days` columns in our `survey` dataframe. Save the results as `drink_dfs`. **DO NOT** include the `()` after `pet_split` when providing it as an argument; it will produce an error. |
| 86 | +::: |
| 87 | + |
| 88 | +```{r, include=answers, results='asis', echo=FALSE} |
| 89 | +cat(' |
| 90 | +::: {.answer} |
| 91 | +drink_dfs = lapply(X = survey[, c("coffee_days", "tea_days", "soda.pop_days", "juice_days", "none_days")], |
| 92 | + FUN = pet_split, |
| 93 | + possible_columns = c("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday")) |
| 94 | +::: |
| 95 | +') |
| 96 | +``` |
| 97 | + |
| 98 | +You will have gotten a list object of length 5 back. Recall that lists are super-vectors. In this case, each element of our list contains an entire dataframe! |
| 99 | + |
| 100 | + |
| 101 | + |
| 102 | + |
| 103 | + |
| 104 | + |
| 105 | + |
| 106 | + |
| 107 | +## Read ALL the Files! |
| 108 | + |
| 109 | +I promised I would share how to read multiple files at once and here we are. This is actually a common way I use `lapply()`. First, we need to have a directory of data files with identical structures. For this example, we will use the data from lab 3 (aggregation and merging). First, specify the path to the data folder of lab 3. You have to find the specific path on your computer. |
| 110 | + |
| 111 | +```{r} |
| 112 | +# change this to match your system |
| 113 | +lab_3_data = "path/to/lab-3-tidy-agg-merge-NAME/data/" |
| 114 | +``` |
| 115 | + |
| 116 | +Next, we will get a vector of all the file paths of the data inside that folder using `list.files()`. I use the *pattern* argument here to say "I only want files that contain this." In this case, it is the "econ_" prefix so I only get our "econ_acs5_YEAR.csv" files. |
| 117 | + |
| 118 | +```{r eval=FALSE} |
| 119 | +econ_data_paths = list.files(lab_3_data, pattern = "econ_", full.names = TRUE) |
| 120 | +``` |
| 121 | + |
| 122 | +Now, we will use `lapply()` to read in all of the econ_X dataframes from that lab at once. |
| 123 | + |
| 124 | +```{r eval=FALSE} |
| 125 | +all_econ_data = lapply(econ_data_paths, read.csv) |
| 126 | +``` |
| 127 | + |
| 128 | +You now have a list of length 6 with all of the econ data from that lab! No copy and pasting required. You can even pivot and merge them all at once too. First, I'll pivot everything from long to wide: |
| 129 | + |
| 130 | +```{r eval=FALSE} |
| 131 | +library(tidyr) |
| 132 | +
|
| 133 | +all_econ_data_wide = lapply(all_econ_data, |
| 134 | + FUN = pivot_wider, |
| 135 | + id_cols = c("GEOID", "NAME"), |
| 136 | + names_from = "variable", |
| 137 | + values_from = c("estimate", "moe")) |
| 138 | +``` |
| 139 | + |
| 140 | +Next, we'll use `basename()` to give each dataframe a year identifier (we'll just use the file name for now). I'll do this one in a `for()` loop as it's easier to match the file names vector and our data list elements. |
| 141 | + |
| 142 | +```{r eval=FALSE} |
| 143 | +for(i in 1:6){ |
| 144 | + |
| 145 | + # get the file name I want |
| 146 | + file_name = basename(econ_data_paths[i]) |
| 147 | + |
| 148 | + # add that as a column to the matching list element |
| 149 | + all_econ_data_wide[[i]]$file_name = file_name |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +Now we can bind them all together. I'll use `do.call()` here. Really this is the only time I ever use it, and I don't know what else it is helpful for. |
| 154 | + |
| 155 | +```{r eval=FALSE} |
| 156 | +merged_econ = do.call(rbind, all_econ_data_wide) |
| 157 | +``` |
| 158 | + |
| 159 | +::: {.question} |
| 160 | +Repeat this process with the "pop_acs5_XXXX" CSVs from lab 3. |
| 161 | +::: |
| 162 | + |
| 163 | +```{r, include=answers, results='asis', echo=FALSE} |
| 164 | +cat(' |
| 165 | +::: {.answer} |
| 166 | +lab_3_data = "path/to/lab-3-tidy-agg-merge-NAME/data/" |
| 167 | +
|
| 168 | +pop_data_paths = list.files(lab_3_data, pattern = "pop_", full.names = TRUE) |
| 169 | +
|
| 170 | +all_pop_data = lapply(pop_data_paths, read.csv) |
| 171 | +
|
| 172 | +all_pop_data_wide = lapply(all_pop_data, |
| 173 | + FUN = pivot_wider, |
| 174 | + id_cols = c("GEOID", "NAME"), |
| 175 | + names_from = "variable", |
| 176 | + values_from = c("estimate", "moe")) |
| 177 | +
|
| 178 | +for(i in 1:6){ |
| 179 | + |
| 180 | + # get the file name I want |
| 181 | + file_name = basename(pop_data_paths[i]) |
| 182 | + |
| 183 | + # add that as a column to the matching list element |
| 184 | + all_pop_data_wide[[i]]$file_name = file_name |
| 185 | +} |
| 186 | +
|
| 187 | +merged_pop = do.call(rbind, all_pop_data_wide) |
| 188 | +::: |
| 189 | +') |
| 190 | +``` |
| 191 | + |
| 192 | + |
| 193 | + |
| 194 | + |
0 commit comments