Want to help out or contribute?

If you find any typos, errors, or places where the text may be improved, please let us know by providing feedback either in the feedback survey (given during class), by using GitLab, or directly in this document with hypothes.is annotations.

  • Open an issue or submitting a merge request on GitLab.
  • Hypothesis Add an annotation using hypothes.is. To add an annotation, select some text and then click the on the pop-up menu. To see the annotations of others, click the in the upper right-hand corner of the page.

7 Save time, don’t repeat yourself

Here we will cover the second block, “Workflow” in Figure 7.1.

Figure 7.1: Section of the overall workflow we will be covering.

And your folder and file structure should look like:

LearnR3
├── data/
│   └── README.md
├── data-raw/
│   ├── mmash-data.zip
│   ├── mmash/
│   │  ├── user_1
│   │  ├── ...
│   │  └── user_22
│   └── mmash.R
├── doc/
│   ├── README.md
│   └── lesson.Rmd
├── R/
│   ├── functions.R
│   └── README.md
├── .Rbuildignore
├── .gitignore
├── DESCRIPTION
├── LearnR3.Rproj
└── README.md

7.1 Learning objectives

  1. Learn what functions are in R, how to create and use them.
  2. Learn about functional programming, vectorization, and functionals, and how to use them to get more done with less code and less time.
  3. Learn a workflow of using R Markdown and devtools::load_all() (or Ctrl-Shift-L in RStudio) as a tool and process for developing functions that can be automatically loaded in for later and easy use.
  4. Learn what R package dependency management is and how it can simplify your data analysis work.
  5. Continue practising Git version control to manage changes to your files.

7.2 The basics of a function

Take 5 min and read this section until it says to stop. As mentioned before, all actions in R are functions. For instance, the + is a function, mean() is a function, [] is a function, and so on. Because R is open source, that means anyone can see how things work underneath. So, if we want to see what a function does underneath, we type out the function name without the () into the Console and run it. If we do it with the function sd() which calculates the standard deviation, we see:

sd
#> function (x, na.rm = FALSE) 
#> sqrt(var(if (is.vector(x) || is.factor(x)) x else as.double(x), 
#>     na.rm = na.rm))
#> <bytecode: 0x55cf4ed3a550>
#> <environment: namespace:stats>

How you calculate the standard deviation is the square root of the variance. In this code, the var() is inside the sqrt() function, which is exactly what it should be.

So, if you learn how to create your own functions, it can make doing your work easier and more efficient because you don’t have to repeat yourself later. Making functions always has a basic structure of:

  1. Giving a name to the function (e.g. mean).
  2. Starting the function call using function(), assigning it to the name with <-. This tells R that the name is a function object.
  3. Optionally providing arguments to give to the function call, for instance function(arg1, arg2, arg3).
  4. Filling out the body of the function, with the arguments (if any) contained inside, that does some action.
  5. Optionally, but strongly encouraged, use return() to indicate what you want the function to output.
For instructors: Click for details

Emphasize that we will be using this workflow for creating functions all the time throughout course and that this workflow is also what you’d use in your daily work.

While there is no minimum or maximum number of arguments you can provide for a function (e.g. you could have zero or dozens of arguments), its good practice for yourself and for others to have as few arguments as possible to get the job done. So, the structure is:

name <- function(arg1, arg2) {
    # body of function
    output <- ... code ....
    return(output)
}

Ok, stop here and we’ll go over it together. Let’s write a simple example. First, create a new Markdown header called ## Making a function and create a code chunk below that with Ctrl-Alt-I. Then, inside the function, let’s write this code out:

add_numbers <- function(num1, num2) {
    added <- num1 + num2
    return(added)
}

You can use the new function by running the above code and writing out your new function, with arguments to give it.

add_numbers(1, 2)
#> [1] 3

The function name is fairly good… add_numbers is read as “add numbers”. While we generally want to write code that describes what it does by reading it, it’s also good practice to add some formal documentation to the function. Use the “Insert Roxygen Skeleton” in the “Code” menu (or by typing Ctrl-Shift-Alt-R) and you can add template documentation right above the function. It looks like:

#' Title
#'
#' @param num1 
#' @param num2 
#'
#' @return
#' @export
#'
#' @examples
add_nums <- function(num1, num2) {
    added <- num1 + num2
    return(added)
}

In the Title area, this is where you type out a brief sentence or several words that describe the function. Creating a new paragraph below this line allows you to add a more detailed description. The other items are:

  • @param num: These lines describe what each argument is for and what to give it.
  • @return: This describes what output the function gives. Is it a data.frame? A plot? What else does the output give?
  • @export: Tells R that this function should be accessible to the user of your package. Since we aren’t making packages, delete it.
  • @examples: Any lines below this are used to show examples of how to use the function. This is very useful when making packages, but not really in this case. So we’ll delete it. Let’s write out some documentation for this function:
#' Add two numbers together.
#'
#' @param num1 A number here.
#' @param num2 A number here.
#'
#' @return Returns the sum of the two numbers.
#'
add_nums <- function(num1, num2) {
    added <- num1 + num2
    return(added)
}

Once we’ve created that, let’s open up the Git Interface (Ctrl-Alt-M) and add and commit these changes to our history.

7.3 Making a function for vroom

Now that we have a basic understanding of what a function looks like, let’s apply it to something we’re doing right now: Importing our data.

Making functions is a series of steps:

  1. Write code that works and does what you want.
  2. Enclose it as a function with name <- function() { ... }, with an appropriate and descriptive name.
  3. Create arguments in the function call (function(arg1, arg2)) with appropriate and descriptive names, then replace the code with the argument names where appropriate.
  4. Rename any objects created to be more generic and include the return() function at the end to indicate what the function will output.
  5. Run the function and check that it works.
  6. Add the roxygen2 documentation tags (with Ctrl-Alt-Shift-R or “Code -> Insert Roxygen Skeleton” menu item while the cursor is in the function).
For instructors: Click for details

Emphasize that we will be using this workflow for creating functions all the time throughout course and that this workflow is also what you’d use in your daily work.

So, step one. Let’s take the code we wrote for importing the user_info data and convert that as a function:

user_1_info_data <- vroom(
    user_1_info_file,
    col_select = -1,
    col_types = cols(
        Gender = col_character(),
        Weight = col_double(),
        Height = col_double(),
        Age = col_double()
    ),
    .name_repair = snakecase::to_snake_case
)

Next we wrap it in the function call and give it an appropriate name. In this case, import_user_info is descriptive and meaningful. Make sure to style it correctly with Ctrl-Shift-A.

import_user_info <- function() {
    user_1_info_data <- vroom(
        user_1_info_file,
        col_select = -1,
        col_types = cols(
            Gender = col_character(),
            Weight = col_double(),
            Height = col_double(),
            Age = col_double()
        ),
        .name_repair = snakecase::to_snake_case
    )
}

Then, we add arguments in the function and replace within the code. Here, we have only one thing that we would change: The file path to the dataset. So, a good name might be file_path.

import_user_info <- function(file_path) {
    user_1_info_data <- vroom(
        file_path,
        col_select = -1,
        col_types = cols(
            Gender = col_character(),
            Weight = col_double(),
            Height = col_double(),
            Age = col_double()
        ),
        .name_repair = snakecase::to_snake_case
    )
}

Then we clean things up by renaming user_1_info_data since we would like to also import more than just user_1. A nice object name would be info_data. Add the return() function at the end with the object you want your function to output.

import_user_info <- function(file_path) {
    info_data <- vroom(
        file_path,
        col_select = -1,
        col_types = cols(
            Gender = col_character(),
            Weight = col_double(),
            Height = col_double(),
            Age = col_double()
        ),
        .name_repair = snakecase::to_snake_case
    )
    return(info_data)
}

Great! Now we need to test it out. Let’s try on two datasets, two user_info.csv files in the user_1 and user_2 folders.

import_user_info(here("data-raw/mmash/user_1/user_info.csv"))
#> # A tibble: 1 x 4
#>   gender weight height   age
#>   <chr>   <dbl>  <dbl> <dbl>
#> 1 M          65    169    29
import_user_info(here("data-raw/mmash/user_2/user_info.csv"))
#> # A tibble: 1 x 4
#>   gender weight height   age
#>   <chr>   <dbl>  <dbl> <dbl>
#> 1 M          95    183    27

Awesome! It works. The final stage is adding the Roxygen documentation.

#' Import MMASH user info data file.
#'
#' @param file_path Path to user info data file.
#'
#' @return Outputs a data frame.
#'
import_user_info <- function(file_path) {
    info_data <- vroom(
        file_path,
        col_select = -1,
        col_types = cols(
            Gender = col_character(),
            Weight = col_double(),
            Height = col_double(),
            Age = col_double()
        ),
        .name_repair = snakecase::to_snake_case
    )
    return(info_data)
}

A massive advantage of using functions is that if you want to make a change to all your code, you can very easily do it by modifying the function and it will change all your other code too. Now that we have a working function, let’s add and commit it to the Git history with the RStudio Git Interface.

7.4 Exercise: Repeat with the saliva data

Time: 15 min

Take the code you created for importing the saliva data set from Exercise 6.4 and make it into a function. A helpful tip: To move around an R Markdown or R script more easily, open up the “Document Outline” on the side by clicking the button in the top right corner of the R Markdown pane or by using Ctrl-Shift-O.

  1. Create a new markdown header ## Exercise for importing the saliva data.
  2. Create a code chunk below that (Ctrl-Alt-I).
  3. Paste the code you used from the exercise into the code chunk and begin converting it into a function, like we did above.
    • Wrap it with the function() {...}
    • Make a meaningful name (e.g. import_saliva)
    • Make an argument for the file path and replace it with the code in vroom()
    • Rename the output object and include it in the return() function
    • Test that it works
    • Create the Roxygen documentation

7.5 Continuing the workflow

We’ve created two functions. Now we need to move those functions from the doc/lesson.Rmd file and into the R/. We do this for a few reasons:

  1. To prevent the R Markdown document from becoming too long and having a large portion of R code over other text.
  2. To make it easier to maintain and find things in your project.
  3. To make use of the devtools::load_all() functionality available with R Projects that are indicated as Packages.

Because this project is set as a Package, we can use the load_all() keyboard shortcut Ctrl-Shift-L in RStudio. But first, we need to make a new file to store our functions. So, in the Console, type out and run:

usethis::use_r("functions")

This creates a new file called R/functions.R. Then we cut and paste the functions we created into this new file. So, cut only the import_user_info() function we created in doc/lesson.Rmd, including the Roxygen documentation, and paste it into R/functions.R.

Once we have it in there, let’s test out how the load_all() function works. Let’s restart our R session with either Ctrl-Shift-F10 or from the “Session -> Restart R” menu item. Move back into the doc/lesson.Rmd and go to where you wrote:

import_user_info(here("data-raw/mmash/user_1/user_info.csv"))

Then hit Ctrl-Shift-L. What happens? You should see a bunch of text pop up on the side about loading the LearnR3 project. Ok. Try to run the import_user_info() line. What happens now? You might get an error about not finding the vroom() function. Or if you put library(vroom) in the code chunk called setup, you might not get an error. If you did get an error, that’s because R doesn’t know what the vroom() function is. This is where we start getting into package dependency management.

What is package dependency management? Whenever you use an R package, you depend on it for your code to work. The informal way to show what packages you use is by using the library() function. But if you come back to the project, or get a new computer, or someone else is working on your project too, how will they know, easily, which packages you depend on? Do they have to search through all your files just to find all library() functions you used and then install those packages individually? A much better way here is to formally indicate your package dependency so that installing dependencies is easy. We do this by using the DESCRIPTION file.

Open up the DESCRIPTION file. You may or may not see something that looks like:

Package: LearnR3
Title: Title of Project
Version: 0.0.0
Authors@R: c(
    person(given = "Luke",
           family = "Johnston",
           role = c("aut", "cre"),
           email = "lwjohnst@gmail.com",
           comment = c(ORCID = "0000-0003-4169-2616"))
    )
Imports: 
    devtools

Notice the Imports: key. This is where information about packages are added. To quickly add a package, go to the Console and type out:

usethis::use_package("vroom")

You will see a bunch of text about adding it to Imports. If you look in your DESCRIPTION file now, you’ll see something like:

Imports: 
    devtools,
    vroom

Now, if you or someone else wants to install all the package dependency, they can do that going to the Console and running:

remotes::install_deps()

This function finds the DESCRIPTION file and installs all the packages in Imports. Let’s add the other dependencies by typing in the Console:

usethis::use_package("here")
usethis::use_package("fs")
usethis::use_package("snakecase")

Great! Now that we’ve formally established package dependencies, we also need to formally declare which package each function comes from inside our functions. Before getting into the correct way, we need to quickly cover the incorrect way that you may or may not have seen that others have done on websites or in code. Sometimes people use library() or require() inside functions like:

add_numbers <- function(num1, num2) {
    library(packagename)
    ...code...
    return(added)
}

Or:

add_numbers <- function(num1, num2) {
    require(packagename)
    ...code...
    return(added)
}

This is very bad practice and can have some unintended and serious consequences that you might not notice or that won’t give any warning or error. The correct way of indicating which package a function comes from is instead by using packagename::, which you’ve seen and used many times in this course. We won’t get into the reasons why this is incorrect because it can quickly get quite technical.

Another reason to use packagename:: for each function from an R package you use in your own function is that it explicitly tells R (and us the readers) where the function comes from. Because the same function name can be used by multiple packages, if you don’t explicitly state which package the function is from, R will use the function that it can find… which isn’t always the function you mean.

We also do this step at the end of making the function because doing it while we create it can be quite tedious. Alright, let’s go into R/functions.R and add vroom:: to each of the vroom functions we’ve used:

import_user_info <- function(file_path) {
    info_data <- vroom::vroom(
        file_path,
        col_select = -1,
        col_types = vroom::cols(
            Gender = vroom::col_character(),
            Weight = vroom::col_double(),
            Height = vroom::col_double(),
            Age = vroom::col_double()
        ),
        .name_repair = snakecase::to_snake_case
    )
    return(info_data)
}

Test that it works by restart the R session (Ctrl-Shift-F10 or “Session -> Restart R”), loading the functions with Ctrl-Shift-L, and going to the Console and typing out:

import_user_info(here::here("data-raw/mmash/user_1/user_info.csv"))

It should work as expected! Now that we’ve done that, let’s add and commit the changes made through the Git interface.

7.6 Exercise: Move and update the rest of the functions

Time: 20 min

Repeat this process and do this for the rest of the code you worked on previously that imported the RR.csv, and Actigraph.csv data.

  1. Convert the importing code into functions while in the doc/lessons.Rmd file. Include the Roxygen documentation and use packagename:: to be explicit about where the function comes from.
    • Name the new functions import_rr and import_actigraph.
  2. Move the function into R/functions.R.
  3. Restart R and then use devtools::load_all() by using Ctrl-Shift-L and test that the functions work by running them in the Console.

Also update the import_saliva() function you created by being explicit about where the functions come from (e.g. with the packagename::).

7.7 Functional programming

Please take 15 min to read over this section before we continue. Unlike many other programming languages, R’s primary strength and approach to programming is in functional programming. So what is functional programming? It is a form of programming that:

  • Uses functions (like function())
  • Applies functions to vectors all at once (called vectorisation), rather than in a loop
  • Can use functions as input to other functions (called a functional)

We’ve already covered functions. You’ve probably already used vectorization since it is one of R’s big strengths. Functions like mean(), sd(), sum() are vectorized in the sense that they take a vector and do something to all the values in the vector at once. As a comparison, in other programming languages, if you wanted to calculate the sum you would need a loop:

total_sum <- 0
# a vector
values <- 1:10
for (value in values) {
    total_sum <- value + total_sum
}
total_sum
#> [1] 55

But in R, you can give a function that uses vectorization the entire vector (e.g. c(1, 2, 3, 4)) and R will know what to do with it. Figure 7.2 shows how a function conceptually uses vectorization.

A function using vectorization. Modified from the [RStudio purrr cheatsheet][purrr-cheatsheet].

Figure 7.2: A function using vectorization. Modified from the RStudio purrr cheatsheet.

Instead, in R, there is the function called sum() that takes the entire vector of values and outputs the total sum, without needing a for loop.

# Vectorized
sum(values)
#> [1] 55

A functional on the other hand is a function that can also use a function as one of its arguments. Figure 7.3 shows how the functional map() from the purrr package works by taking a vector (or list), applying a function to each of those items, and outputting the results from each function. The name map() doesn’t mean a geographic map, it means the mathematical meaning of map: To use a function on each item in a set of items.

A functional that uses a function to apply it to each item in a vector. Modified from the [RStudio purrr cheatsheet][purrr-cheatsheet].

Figure 7.3: A functional that uses a function to apply it to each item in a vector. Modified from the RStudio purrr cheatsheet.

Here’s a simple toy example to show how it works. Here we use paste() on each item of 1:5.

library(purrr)
map(1:5, paste)
#> [[1]]
#> [1] "1"
#> 
#> [[2]]
#> [1] "2"
#> 
#> [[3]]
#> [1] "3"
#> 
#> [[4]]
#> [1] "4"
#> 
#> [[5]]
#> [1] "5"

You’ll notice that map() outputs a list, with all the [[1]] printed. map() will always output a list. Also notice that the paste() function is given without the () brackets. Without the brackets, the function can be used by the map() functional and treated like any other object in R.

Let’s say we wanted to paste together the number with the sentence “seconds have passed”. Normally it would look like:

paste(1, "seconds have passed")
#> [1] "1 seconds have passed"
paste(2, "seconds have passed")
#> [1] "2 seconds have passed"
paste(3, "seconds have passed")
#> [1] "3 seconds have passed"
paste(4, "seconds have passed")
#> [1] "4 seconds have passed"
paste(5, "seconds have passed")
#> [1] "5 seconds have passed"

Or as a loop:

for (num in 1:5) {
    sec_passed <- paste(num, "seconds have passed")
    print(sec_passed)
}
#> [1] "1 seconds have passed"
#> [1] "2 seconds have passed"
#> [1] "3 seconds have passed"
#> [1] "4 seconds have passed"
#> [1] "5 seconds have passed"

With map(), we’d do this a bit differently. purrr uses a shortcut to allow you to write functions that do more things to the input vector (e.g. 1:5). This shortcut is using ~ to start the function and .x as the replacement for the vector item:

map(1:5, ~paste(.x, "seconds have passed"))
#> [[1]]
#> [1] "1 seconds have passed"
#> 
#> [[2]]
#> [1] "2 seconds have passed"
#> 
#> [[3]]
#> [1] "3 seconds have passed"
#> 
#> [[4]]
#> [1] "4 seconds have passed"
#> 
#> [[5]]
#> [1] "5 seconds have passed"

This is the basics of using functionals. So, with functions, vectorization, and functionals, you generally should only use for loops as the very last option. That’s because R has better, more expressive, and more powerful tools to use instead. And while technically, using a loop let’s you “not repeat yourself”, it’s a lot of typing and code to express a simple idea: Do an action on each of these items. So using functionals let’s you repeat yourself less 😄

So what does functionals have to do with what we are doing now? Well, our import_user_info() function only takes in one data file. But we have right now 22 files. We can use functionals to load in all the datasets at once! Ok, stop reading and we’ll go over this again before continuing with the coding.

For instructors: Click for details

Go over this section briefly by reinforcing what they read. Make sure they understand the concept of applying something to many things at once. Doing the code-along should also help reinforce this concept.

Also highlight that the resources appendix has some links for continued learning for this and that the RStudio purrr cheatsheet is an amazing resource to use.

The first thing we have to do is add library(purrr) to the setup code chunk in the doc/lesson.Rmd document. Then we need to add the package dependency by going to the Console and running:

usethis::use_package("purrr")

Then, the next step to actually using the map() functional is to get a vector or list of all the dataset files available to us. We turn back to use the fs package, which has a function called dir_ls() that finds files of a certain pattern. In our case, the pattern is user_info.csv. So, let’s add library(fs) to the setup code chunk. Then, go to the bottom of the doc/lesson.Rmd document, create a new header called ## Using map, and create a code chunk below that.

The dir_ls() function takes the path that we want to search (data-raw/mmash/), uses the argument regexp (short for regular expression or also regex) to find the pattern, and recurse to look in all subfolders. We’ll cover regular expressions a bit more in next session.

user_info_files <- dir_ls(here("data-raw/mmash/"), 
                          regexp = "user_info.csv", 
                          recurse = TRUE)

Then let’s see what the output looks like. For the website, we are only showing the first 3 files. Your output will look slightly different from this.

user_info_files
#> data-raw/mmash/user_1/user_info.csv  data-raw/mmash/user_10/user_info.csv 
#> data-raw/mmash/user_11/user_info.csv

Alright, we now have all the files ready to give to map(). So let’s try it!

user_info_list <- map(user_info_files, import_user_info)

Remember, that map() always outputs a list, so when we look into this object, it will give us 22 tibbles (data.frames). Here we’ll only show the first one:

user_info_list[[1]]
#> # A tibble: 1 x 4
#>   gender weight height   age
#>   <chr>   <dbl>  <dbl> <dbl>
#> 1 M          65    169    29

This is great because with one line of code we imported all these datasets! But we’re missing an important bit of information: The user ID. A powerful feature of the purrr package is that it has other functions to make working with functionals easier. We know map() always outputs a list. What if you want to output a character vector instead? If we check the help:

?map
For instructors: Click for details

Go through this help documentation and talk a bit about it.

We see that there are other functions, including a function called map_chr() that seems to output a character vector. There are several others that give an output based on the ending of map_, such as:

  • map_int() outputs an integer.
  • map_dbl() outputs a numeric value, called a “double” in programming.
  • map_dfr() outputs a data frame, combining the list items by row (r).
  • map_dfc() outputs a data frame, combining the list items by column (c).

The map_dfr() looks like the one we want, since we want all these datasets together as one. If we look at the help for it, we see that it has an argument .id, which we can use to create a new column that sets the user ID, or in this case, the file path to the dataset, which has the user ID information in it. So, let’s use it and create a new column called file_path_id.

user_info_df <- map_dfr(user_info_files, import_user_info,
                        .id = "file_path_id")

Your file_path_id variable will look different. Don’t worry, we’re going to tidy up the file_path_id variable later.

user_info_df
#> # A tibble: 22 x 5
#>    file_path_id                         gender weight height   age
#>    <chr>                                <chr>   <dbl>  <dbl> <dbl>
#>  1 data-raw/mmash/user_1/user_info.csv  M          65    169    29
#>  2 data-raw/mmash/user_10/user_info.csv M          85    180    27
#>  3 data-raw/mmash/user_11/user_info.csv M         115    186    27
#>  4 data-raw/mmash/user_12/user_info.csv M          67    170    27
#>  5 data-raw/mmash/user_13/user_info.csv M          74    180    25
#>  6 data-raw/mmash/user_14/user_info.csv M          64    171    27
#>  7 data-raw/mmash/user_15/user_info.csv M          80    180    24
#>  8 data-raw/mmash/user_16/user_info.csv M          67    176    27
#>  9 data-raw/mmash/user_17/user_info.csv M          60    175    24
#> 10 data-raw/mmash/user_18/user_info.csv M          80    180     0
#> # … with 12 more rows

Now that we have this working, let’s add and commit the changes to the Git history.

7.8 Exercise: Make a function for importing other datasets with functionals

Time: 20 min

We need to do basically the exact same thing for the saliva.csv, RR.csv, and Actigraph.csv datasets, following this format:

user_info_files <- dir_ls(here("data-raw/mmash/"), 
                          regexp = "user_info.csv", 
                          recurse = TRUE)
user_info_df <- map_dfr(user_info_files, import_user_info,
                        .id = "file_path_id")

For importing the other datasets, we basically only have to modify two locations to get this code to import the other datasets: at the regexp = argument and where the import_user_info function is. This is the perfect chance to make a function that you can use for other purposes and that is itself a functional (since it takes a function as an input). So inside doc/lesson.Rmd, convert this bit of code into a function that works to import the other three datasets.

  1. Create a new header ## Exercise: Map on the other datasets at the bottom of the document.
  2. Create a new code chunk below it.
  3. Repeat the steps you’ve taken previously to create a new function:
    • Wrap the code with function() { ... }
    • Name the function import_multiple_files
    • Create two new arguments called file_pattern and import_function
    • Create generic intermediate objects (instead of user_info_file and user_info_df). I’d recommend something like data_files and combined_data
    • Use return() at the end of the function for the object you want to output
    • Create and write Roxygen documentation to describe the new function
    • Append packagename:: to the individual functions (there are three packages used: fs, here, and purrr)
    • Run it and check that it works on saliva.csv 1 After it works, cut and paste the function into the R/functions.R file. Then restart the R session, run load_all(), and test the code out in the Console.
  4. Then, write code in your doc/lesson.Rmd file where the function used to be and import in the other datasets. You should now have three new objects for all the imported datasets, called saliva_df, rr_df, and actigraph_df.
  5. Once done, add the changes you’ve made and commit them to the Git history.