7  Manipulate data frames

As usual, let’s start by uploading the needed libraries. The usual tidyverse.

# ETL
library(tidyverse)

In this part we are going to look at the most classic manipulations of data.

The first part is related to simple transformations, and focuses on the classic operations that we perform on a data frame, such as adding variables, sorting, filtering or “if then else” statements.

The intermediate part is about transforming tables. We will see how to create some summary tables, pivote data and merge data frames.

8 Create 2 demo datasets

first dataset

A data frame with 3 variables :

  • some cities (of the South West of France).

  • their population.

  • the departments (i.e geographical region) where are located those cities.

# create 3 vectors
Cities <- c("La Rochelle", "Saintes", "Rochefort", "Royan", "Aytre", "Tonnay-Charente", "Saint-Jean-d’Angely", "Perigny", "Angouleme", "Cognac")

Population <- c(74880, 25586, 25183, 17875, 8970, 7739, 7702, 7456, 41740, 18825)

Departments <- c("Charente Maritime","Charente Maritime","Charente Maritime","Charente Maritime","Charente Maritime","Charente Maritime","Charente Maritime","Charente Maritime","Charente","Charente")

# combine into 1 dataframe
Geo_data <- data.frame(Cities,
                       Population,
                       Departments)

# display data frame
Geo_data
                Cities Population       Departments
1          La Rochelle      74880 Charente Maritime
2              Saintes      25586 Charente Maritime
3            Rochefort      25183 Charente Maritime
4                Royan      17875 Charente Maritime
5                Aytre       8970 Charente Maritime
6      Tonnay-Charente       7739 Charente Maritime
7  Saint-Jean-d’Angely       7702 Charente Maritime
8              Perigny       7456 Charente Maritime
9            Angouleme      41740          Charente
10              Cognac      18825          Charente

second dataset

Similar to the previous dataset, with other cities, also located in the South West of France, but this time in one department called “Gironde”.

# create 3 vectors
Cities <- c("Bordeaux", "Merignac", "Pessac", "Talence")

Population <- c(254436, 70105,63808,42606)

Departments <- c("Gironde","Gironde","Gironde","Gironde")

# combine into 1 dataframe
Geo_data2 <- data.frame(Cities,
                      Population,
                      Departments)

# display data frame
Geo_data2
    Cities Population Departments
1 Bordeaux     254436     Gironde
2 Merignac      70105     Gironde
3   Pessac      63808     Gironde
4  Talence      42606     Gironde

9 The pipe operator

The “pipe” operator is a very useful sign to chain operations. We can write it in 2 ways :

  • %>% (old way, but still used)

  • |> (new way)

The operator |> means “and then”.

We can use it when first we select an object, and then we perform an action on it. The selected object is written before the |> operator, and the action is written after.

For example [Object1] |> [do this action] means “take the [Object1] and then perform this action on it”

Let’s illustrate this in the following parts.

10 10 Simple Transformations

10.1 select()

the function select() keeps or discards columns (aka variables)

Figure 36 : the function select()

Let’s using the data frame Geo_data, and select only 2 variables : Cities and Population.

We can write the following syntax, using the |> operator, which means: “take the object [Geo_data] and then perform the action select() on it” (selection of only 2 variables: Cities and Population)

Geo_data |> select(Cities, Population)
                Cities Population
1          La Rochelle      74880
2              Saintes      25586
3            Rochefort      25183
4                Royan      17875
5                Aytre       8970
6      Tonnay-Charente       7739
7  Saint-Jean-d’Angely       7702
8              Perigny       7456
9            Angouleme      41740
10              Cognac      18825

Can drop columns with -column

Using the function select() and adding a minus sign “-” before the name of a variable (or several variables), we can remove this variable from the data frame :

Geo_data |> select(-Departments)
                Cities Population
1          La Rochelle      74880
2              Saintes      25586
3            Rochefort      25183
4                Royan      17875
5                Aytre       8970
6      Tonnay-Charente       7739
7  Saint-Jean-d’Angely       7702
8              Perigny       7456
9            Angouleme      41740
10              Cognac      18825

Reorder columns

We also can use the function select() to select the variables we need and display them in a particular order inside the data frame :

Geo_data |> select(Departments, Cities, Population)
         Departments              Cities Population
1  Charente Maritime         La Rochelle      74880
2  Charente Maritime             Saintes      25586
3  Charente Maritime           Rochefort      25183
4  Charente Maritime               Royan      17875
5  Charente Maritime               Aytre       8970
6  Charente Maritime     Tonnay-Charente       7739
7  Charente Maritime Saint-Jean-d’Angely       7702
8  Charente Maritime             Perigny       7456
9           Charente           Angouleme      41740
10          Charente              Cognac      18825

everything()

Using the function everything(), we can bring a specific variable in the first or last position. Just need to type the name of the variable we want to relocate, and everything(), before or after it :

Geo_data |> select(Population, everything())
   Population              Cities       Departments
1       74880         La Rochelle Charente Maritime
2       25586             Saintes Charente Maritime
3       25183           Rochefort Charente Maritime
4       17875               Royan Charente Maritime
5        8970               Aytre Charente Maritime
6        7739     Tonnay-Charente Charente Maritime
7        7702 Saint-Jean-d’Angely Charente Maritime
8        7456             Perigny Charente Maritime
9       41740           Angouleme          Charente
10      18825              Cognac          Charente

10.2 add a variable

Let’s create a new variable (a column in the data frame) and perform a simple calculation. We use the approach we saw previously :

Figure 37 : add a variable to a data frame
  • first, let’s create a clone, or copy, of the object Geo_data2 (so we don’t modify the initial data frame Geo_data2). We’ll call it “mycopy”. It’s an useful practice to keep some initial data.

  • then let’s create a new variable called “mynew_variable”.

  • this new variable is the result of a very simple calculation : we take the variable “Population” and multiply it by 100.

# create a copy of Geo_data2
mycopy <- Geo_data2

# create a variable, and perform a simple 
mycopy$mynew_variable <- mycopy$Population*100

# display data frame
mycopy
    Cities Population Departments mynew_variable
1 Bordeaux     254436     Gironde       25443600
2 Merignac      70105     Gironde        7010500
3   Pessac      63808     Gironde        6380800
4  Talence      42606     Gironde        4260600

We can see that we now have a new variable, called “mynew_variable”, which is the result of the variable “Population”, multiplied by 100, for each row.

R performs operations by default along a variable (column) of a data frame.

Now, let’s add one new column with a character.

As previously, the same character value will be written along all the rows of the data frame.

# add one column Region
mycopy$Region <- "Nouvelle Aquitaine"

# display data frame
mycopy
    Cities Population Departments mynew_variable             Region
1 Bordeaux     254436     Gironde       25443600 Nouvelle Aquitaine
2 Merignac      70105     Gironde        7010500 Nouvelle Aquitaine
3   Pessac      63808     Gironde        6380800 Nouvelle Aquitaine
4  Talence      42606     Gironde        4260600 Nouvelle Aquitaine

10.3 rbind() & cbind()

Very often, we need to combine several data frames into a single one, for example :

  • historical sales : historical sales data can be recorded for each month or year, inside distinct data frames.

  • inventories of different locations : stocks can be recorded separately in different warehouses (related to each country) or storage locations (duty free, duty paid,…).

  • etc…

The functions rbind() and cbind() will allow us to easily combine -bind- some data frames together.

Figure 38 : the functions rbind() & cbind()

10.3.1 rbind()

The rbind() function allows to stack several data frames together.

One prerequisite, those data frames must have:

  • the same number of variables.

  • the same names of variables.

  • the same class of variables.

# combine 2 data frames
All_Geo_data <- rbind(Geo_data, Geo_data2)

# display data frame
All_Geo_data
                Cities Population       Departments
1          La Rochelle      74880 Charente Maritime
2              Saintes      25586 Charente Maritime
3            Rochefort      25183 Charente Maritime
4                Royan      17875 Charente Maritime
5                Aytre       8970 Charente Maritime
6      Tonnay-Charente       7739 Charente Maritime
7  Saint-Jean-d’Angely       7702 Charente Maritime
8              Perigny       7456 Charente Maritime
9            Angouleme      41740          Charente
10              Cognac      18825          Charente
11            Bordeaux     254436           Gironde
12            Merignac      70105           Gironde
13              Pessac      63808           Gironde
14             Talence      42606           Gironde

10.3.2 cbind()

The cbind() function can be used to add one or several columns (i.e. a data frame) to one data frame.

In the following example example, we will “stick” the vector “Continent” to the data frame Geo_data2 :

# create a vector called "Continent"
Continent <- c("Europe","Europe","Europe","Europe")

# combine with Geo_data2
cbind(Geo_data2, Continent)
    Cities Population Departments Continent
1 Bordeaux     254436     Gironde    Europe
2 Merignac      70105     Gironde    Europe
3   Pessac      63808     Gironde    Europe
4  Talence      42606     Gironde    Europe

let’s explore the “recycling” feature

This vector “Continent” has 4 values. What happens if it had only 2 elements ?

This leads us to a sometimes very useful feature called “recycling”.

Let’s create a new vector “Continent” with only 2 elements this time, and bind it to the data frame Geo_data2.

We can see that the 2 elements are repeated on the following rows. This is the “recycling” feature.

# create a vector with just 2 elements
Continent <- c("Europe","To be confirmed")

# combine with Geo_data2
cbind(Geo_data2, Continent)
    Cities Population Departments       Continent
1 Bordeaux     254436     Gironde          Europe
2 Merignac      70105     Gironde To be confirmed
3   Pessac      63808     Gironde          Europe
4  Talence      42606     Gironde To be confirmed

10.4 arrange()

The arrange() function allows us to sort a data frame based on one or several variables.

Let’s practice on the data frame All_Geo_data.

Figure 30 : the function arrange()

sort by ascending order

In the 2 examples below, we will sort from the beginning to the end :

  • by alphabetical order

  • by numerical values

By alphabetical order :

All_Geo_data |> arrange(Cities)
                Cities Population       Departments
1            Angouleme      41740          Charente
2                Aytre       8970 Charente Maritime
3             Bordeaux     254436           Gironde
4               Cognac      18825          Charente
5          La Rochelle      74880 Charente Maritime
6             Merignac      70105           Gironde
7              Perigny       7456 Charente Maritime
8               Pessac      63808           Gironde
9            Rochefort      25183 Charente Maritime
10               Royan      17875 Charente Maritime
11 Saint-Jean-d’Angely       7702 Charente Maritime
12             Saintes      25586 Charente Maritime
13             Talence      42606           Gironde
14     Tonnay-Charente       7739 Charente Maritime

By numerical values :

All_Geo_data |> arrange(Population)
                Cities Population       Departments
1              Perigny       7456 Charente Maritime
2  Saint-Jean-d’Angely       7702 Charente Maritime
3      Tonnay-Charente       7739 Charente Maritime
4                Aytre       8970 Charente Maritime
5                Royan      17875 Charente Maritime
6               Cognac      18825          Charente
7            Rochefort      25183 Charente Maritime
8              Saintes      25586 Charente Maritime
9            Angouleme      41740          Charente
10             Talence      42606           Gironde
11              Pessac      63808           Gironde
12            Merignac      70105           Gironde
13         La Rochelle      74880 Charente Maritime
14            Bordeaux     254436           Gironde

sort by descending order

We can add and use the function desc() to sort by descending order, as follow :

All_Geo_data |> arrange(desc(Population))
                Cities Population       Departments
1             Bordeaux     254436           Gironde
2          La Rochelle      74880 Charente Maritime
3             Merignac      70105           Gironde
4               Pessac      63808           Gironde
5              Talence      42606           Gironde
6            Angouleme      41740          Charente
7              Saintes      25586 Charente Maritime
8            Rochefort      25183 Charente Maritime
9               Cognac      18825          Charente
10               Royan      17875 Charente Maritime
11               Aytre       8970 Charente Maritime
12     Tonnay-Charente       7739 Charente Maritime
13 Saint-Jean-d’Angely       7702 Charente Maritime
14             Perigny       7456 Charente Maritime

sort by multiple variables

Now we are going to execute one after each other 2 different sortings :

  • first : sort by ascending Departments.

  • second : inside each Department, sort by descending Population.

The syntax is as follow :

All_Geo_data |> arrange(Departments, 
                      desc(Population)
                      )
                Cities Population       Departments
1            Angouleme      41740          Charente
2               Cognac      18825          Charente
3          La Rochelle      74880 Charente Maritime
4              Saintes      25586 Charente Maritime
5            Rochefort      25183 Charente Maritime
6                Royan      17875 Charente Maritime
7                Aytre       8970 Charente Maritime
8      Tonnay-Charente       7739 Charente Maritime
9  Saint-Jean-d’Angely       7702 Charente Maritime
10             Perigny       7456 Charente Maritime
11            Bordeaux     254436           Gironde
12            Merignac      70105           Gironde
13              Pessac      63808           Gironde
14             Talence      42606           Gironde

We start by writing within the function arrange() the variable “Departments” and then the variable “Population”, itself within the function desc().

10.5 filter()

We can filter a data frame based on :

  • a variable.

  • multiple variables.

  • one or several values within those variables.

Figure 40 : the function filter()

Filter : test for equality or multiple values

The function filter() keeps or discards rows (i.e observations) based on one or several values.

  • the == operator tests for equality, referring to a single value (note that it comes with 2 signs =).

  • the %in% operator filters on several values. Those values are contain inside a vector.

Let’s practice on the data frame All_Geo_data :

# filter on Departments = Gironde
All_Geo_data |> filter(Departments == "Gironde")
    Cities Population Departments
1 Bordeaux     254436     Gironde
2 Merignac      70105     Gironde
3   Pessac      63808     Gironde
4  Talence      42606     Gironde

Now let’s filter, to keep 2 departments, “Charente” and “Gironde”. We will write those 2 departments inside a vector, and use the %in% operator to say that we want to select several variables.

# filter on Departments Charente and Gironde 
All_Geo_data |> filter(Departments %in% c("Charente","Gironde"))
     Cities Population Departments
1 Angouleme      41740    Charente
2    Cognac      18825    Charente
3  Bordeaux     254436     Gironde
4  Merignac      70105     Gironde
5    Pessac      63808     Gironde
6   Talence      42606     Gironde

Filter <= or a range, using both >= & <=

Now let’s filter to keep all the rows with a population below 10.000 inhabitants.

# filter on Population below 10000
All_Geo_data |> filter(Population <= 10000)
               Cities Population       Departments
1               Aytre       8970 Charente Maritime
2     Tonnay-Charente       7739 Charente Maritime
3 Saint-Jean-d’Angely       7702 Charente Maritime
4             Perigny       7456 Charente Maritime

We also can filter to keep all the rows with a population between 10.000 and 50.000 inhabitants.

# filter on Population between 10000 & 50000
All_Geo_data |> filter(Population >= 10000 & Population <= 50000)
     Cities Population       Departments
1   Saintes      25586 Charente Maritime
2 Rochefort      25183 Charente Maritime
3     Royan      17875 Charente Maritime
4 Angouleme      41740          Charente
5    Cognac      18825          Charente
6   Talence      42606           Gironde

Filter : the | operator means “or”

We can combine 2 filters, applied on 2 different variables. Here is an example using the “or” operator, displayed as “|”.

We keep all the rows with a population above 70.000 inhabitants, OR from the Department “Charente” (whatever is the population of those cities).

All_Geo_data |> filter(Departments == "Charente" | Population >= 70000)
       Cities Population       Departments
1 La Rochelle      74880 Charente Maritime
2   Angouleme      41740          Charente
3      Cognac      18825          Charente
4    Bordeaux     254436           Gironde
5    Merignac      70105           Gironde

Filter : the & operator

We keep all the rows with a population below 50.000 inhabitants, AND from the Departments “Charente” & “Gironde”.

All_Geo_data |> filter(Departments %in% c("Charente","Gironde") & Population <= 50000)
     Cities Population Departments
1 Angouleme      41740    Charente
2    Cognac      18825    Charente
3   Talence      42606     Gironde

exclude value

We also can filter to exclude one or several values. In this case we will use the sign “!” before the variable that we want to filter.

In the example below, we want to exclude the rows which contain 2 cities (La Rochelle and Royan) in the data frame All_Geo_data .

All_Geo_data |> filter(!Cities %in% c("La Rochelle", "Royan"))
                Cities Population       Departments
1              Saintes      25586 Charente Maritime
2            Rochefort      25183 Charente Maritime
3                Aytre       8970 Charente Maritime
4      Tonnay-Charente       7739 Charente Maritime
5  Saint-Jean-d’Angely       7702 Charente Maritime
6              Perigny       7456 Charente Maritime
7            Angouleme      41740          Charente
8               Cognac      18825          Charente
9             Bordeaux     254436           Gironde
10            Merignac      70105           Gironde
11              Pessac      63808           Gironde
12             Talence      42606           Gironde

10.6 count()

The function count() tallies a data frame by certain variable(s).

It’s useful to get a summary of the number of occurrences of some values.

All_Geo_data |> count(Departments)
        Departments n
1          Charente 2
2 Charente Maritime 8
3           Gironde 4

10.7 unique()

During our data manipulations, we will often need to handle duplicated values.

Sometimes, we will want to keep only the unique rows of a data frame.

This can be done easily by applying the function unique() to a data frame. To illustrate it, let’s create a data frame called df.

It’s also the opportunity to introduce here the function rep(), which provides an easy way to replicate one value.

For example, instead on writing: a <- c("A","A","A","B","B","B","C","C")

we can write : a <- c(rep("A", 3), rep("B", 3), rep("C",2))

# create a dummy dataframe
a <- c(rep("A", 3), rep("B", 3), rep("C",2))
b <- c(1,1,2,4,4,4,2,2)

# combine into 1 dataframe
df <- data.frame(a,b)

# display data frame
df
  a b
1 A 1
2 A 1
3 A 2
4 B 4
5 B 4
6 B 4
7 C 2
8 C 2

We can notice that some rows appear several times :

  • A & 1 : 2 times.

  • B & 4 : 3 times.

  • C & 2 : 2 times.

Let’s keep only the unique rows within the whole data frame.

Applying the function unique() we will remove the duplicated values.

# another way to select the unique values
df1 <- unique(df)

# display data frame
df1
  a b
1 A 1
3 A 2
4 B 4
7 C 2

10.8 str_sub()

Let’s explore the function str_sub() from the R package stringr. This is the equivalent of the 2 functions left and right in Excel.

We continue practicing on the data frame All_Geo_data :

  • Extract of the first 3 characters of the variable “Cities”.

    • we specify that we start from the value number 1, and end at the value number 3.
  • Extract of the last 3 characters of the variable “Cities”.

    • we specify that we start from the last value number 1, and end at the value number 3.

    • putting a minus sign “-” before the values 1 and 3.

# Extract the first 3 characters
str_sub(All_Geo_data$Cities, 1, 3)
 [1] "La " "Sai" "Roc" "Roy" "Ayt" "Ton" "Sai" "Per" "Ang" "Cog" "Bor" "Mer"
[13] "Pes" "Tal"
# Extract the last 3 characters
str_sub(All_Geo_data$Cities, -3, -1)
 [1] "lle" "tes" "ort" "yan" "tre" "nte" "ely" "gny" "eme" "nac" "aux" "nac"
[13] "sac" "nce"

10.9 if then else

Writing an “if then else” statement is relatively easy in R, we can use the function if_else().

The function if_else() comes with 3 arguments: condition, value if true, value if false.

In the example below, we are going to create 2 locations, South and North, based on the Department. We say that if the Department is “Gironde”, then the location is the South.

# let's create a copy of the dataframe
df1 <- All_Geo_data 

# create the variable Location
df1$Location <- if_else(df1$Departments == "Gironde", "South", "North")

# display data frame
df1
                Cities Population       Departments Location
1          La Rochelle      74880 Charente Maritime    North
2              Saintes      25586 Charente Maritime    North
3            Rochefort      25183 Charente Maritime    North
4                Royan      17875 Charente Maritime    North
5                Aytre       8970 Charente Maritime    North
6      Tonnay-Charente       7739 Charente Maritime    North
7  Saint-Jean-d’Angely       7702 Charente Maritime    North
8              Perigny       7456 Charente Maritime    North
9            Angouleme      41740          Charente    North
10              Cognac      18825          Charente    North
11            Bordeaux     254436           Gironde    South
12            Merignac      70105           Gironde    South
13              Pessac      63808           Gironde    South
14             Talence      42606           Gironde    South

Here is another example with if else. This time, let’s use the initial data frame Geo_data.

Based on the population of each city, we will define a variable called “myrange”, to identify whether there are a “lot of people” (i.e. if more than 30.000 inhabitants), or “not many”, if it’s not the case.

# let's create a copy of the dataframe
df1 <- Geo_data

# affect a range
df1$myrange <- if_else(df1$Population > 30000, "a lot of people", "not many")

# display data frame
df1
                Cities Population       Departments         myrange
1          La Rochelle      74880 Charente Maritime a lot of people
2              Saintes      25586 Charente Maritime        not many
3            Rochefort      25183 Charente Maritime        not many
4                Royan      17875 Charente Maritime        not many
5                Aytre       8970 Charente Maritime        not many
6      Tonnay-Charente       7739 Charente Maritime        not many
7  Saint-Jean-d’Angely       7702 Charente Maritime        not many
8              Perigny       7456 Charente Maritime        not many
9            Angouleme      41740          Charente a lot of people
10              Cognac      18825          Charente        not many

We can refine this variable “myrange” :

  • add one more range : if between 10.000 and 20.000 inhabitants, “it looks like a middle size city”.

  • otherwise, keep the previous value of “myrange”.

We can write the following syntax :

# Add a new range
df1$myrange <- if_else(df1$Population > 10000 & df1$Population <= 30000, "it looks like a middle size city", df1$myrange)

# display data frame
df1
                Cities Population       Departments
1          La Rochelle      74880 Charente Maritime
2              Saintes      25586 Charente Maritime
3            Rochefort      25183 Charente Maritime
4                Royan      17875 Charente Maritime
5                Aytre       8970 Charente Maritime
6      Tonnay-Charente       7739 Charente Maritime
7  Saint-Jean-d’Angely       7702 Charente Maritime
8              Perigny       7456 Charente Maritime
9            Angouleme      41740          Charente
10              Cognac      18825          Charente
                            myrange
1                   a lot of people
2  it looks like a middle size city
3  it looks like a middle size city
4  it looks like a middle size city
5                          not many
6                          not many
7                          not many
8                          not many
9                   a lot of people
10 it looks like a middle size city

We kept the previous values in my range and added some new ones.

This could also be seen as simple way to “nest” some “if then else” statements, through different steps.

We will see in the next part that there are other functions, such as case_when() which allows to create different cases, based on a range of values.

10.10 gsub()

When we clean some data, we often need to replace some values. The function gsub() will help us to implement those changes

It works as follow : gsub(pattern, replacement, targeted variable)

Let’s apply it on the data frame Geo_data2 :

# Create a working df
df1 <- Geo_data2

# substituting the value Bordeaux by Paris using gsub()
df1$Cities <- gsub("Bordeaux", "Paris the capital",df1$Cities)

# display data frame
df1
             Cities Population Departments
1 Paris the capital     254436     Gironde
2          Merignac      70105     Gironde
3            Pessac      63808     Gironde
4           Talence      42606     Gironde

We replaced the value “Bordeaux” by the group of words “Paris is the capital”.

11 10 Intermediate Transformations

11.1 mutate()

The fonction mutate() creates one or several new variables. The new variables come with a single “= .

In the example below we just had one variable, and it’s somehow similar to the syntax : dataset$my.new.variable.

All_Geo_data |> mutate(Country = "France")
                Cities Population       Departments Country
1          La Rochelle      74880 Charente Maritime  France
2              Saintes      25586 Charente Maritime  France
3            Rochefort      25183 Charente Maritime  France
4                Royan      17875 Charente Maritime  France
5                Aytre       8970 Charente Maritime  France
6      Tonnay-Charente       7739 Charente Maritime  France
7  Saint-Jean-d’Angely       7702 Charente Maritime  France
8              Perigny       7456 Charente Maritime  France
9            Angouleme      41740          Charente  France
10              Cognac      18825          Charente  France
11            Bordeaux     254436           Gironde  France
12            Merignac      70105           Gironde  France
13              Pessac      63808           Gironde  France
14             Talence      42606           Gironde  France

But we can add more actions (variables), for example :

All_Geo_data |> mutate(Country = "France",
                       Continent = "Europe")
                Cities Population       Departments Country Continent
1          La Rochelle      74880 Charente Maritime  France    Europe
2              Saintes      25586 Charente Maritime  France    Europe
3            Rochefort      25183 Charente Maritime  France    Europe
4                Royan      17875 Charente Maritime  France    Europe
5                Aytre       8970 Charente Maritime  France    Europe
6      Tonnay-Charente       7739 Charente Maritime  France    Europe
7  Saint-Jean-d’Angely       7702 Charente Maritime  France    Europe
8              Perigny       7456 Charente Maritime  France    Europe
9            Angouleme      41740          Charente  France    Europe
10              Cognac      18825          Charente  France    Europe
11            Bordeaux     254436           Gironde  France    Europe
12            Merignac      70105           Gironde  France    Europe
13              Pessac      63808           Gironde  France    Europe
14             Talence      42606           Gironde  France    Europe

It’s also often used when we want to chain different operations.

In the example below :

  • first we use an “if then else” statement and create a new variable called “Location”.

  • then, we apply on this result the function select() to keep only 2 variables, “Cities” and (the newly created) “Location”.

All_Geo_data |> 
    mutate(Location = if_else(Departments == "Gironde", "South", "North")) |>  
    select(Cities, Location)
                Cities Location
1          La Rochelle    North
2              Saintes    North
3            Rochefort    North
4                Royan    North
5                Aytre    North
6      Tonnay-Charente    North
7  Saint-Jean-d’Angely    North
8              Perigny    North
9            Angouleme    North
10              Cognac    North
11            Bordeaux    South
12            Merignac    South
13              Pessac    South
14             Talence    South

11.2 case_when()

We previously looked at the “if then else” statement.

When there are multiple cases, the function case_when() can make our analysis much easier !

In the example below, we will define 3 cases :

  • first case : some cities are called “regional capitals”.

  • second case : another group of cities is called “small towns”.

  • third case : another group of cities is called “very small towns”.

  • the other cities are captured in a group called “to be defined”.

We will create a new variable using the function mutate(), and use the function case_when() to define those different cases, in this new variable.

All_Geo_data |> mutate(city_group = case_when(
  Cities %in% c("La Rochelle", "Angouleme", "Bordeaux") ~ "regional capitals",
  Cities %in% c("Saintes", "Rochefort", "Cognac", "Pessac", "Merignac", "Talence") ~ "small towns",
  Cities %in% c("Royan", "Aytre", "Tonnay-Charente") ~ "very small towns",
  TRUE ~ "to be defined") 
  )
                Cities Population       Departments        city_group
1          La Rochelle      74880 Charente Maritime regional capitals
2              Saintes      25586 Charente Maritime       small towns
3            Rochefort      25183 Charente Maritime       small towns
4                Royan      17875 Charente Maritime  very small towns
5                Aytre       8970 Charente Maritime  very small towns
6      Tonnay-Charente       7739 Charente Maritime  very small towns
7  Saint-Jean-d’Angely       7702 Charente Maritime     to be defined
8              Perigny       7456 Charente Maritime     to be defined
9            Angouleme      41740          Charente regional capitals
10              Cognac      18825          Charente       small towns
11            Bordeaux     254436           Gironde regional capitals
12            Merignac      70105           Gironde       small towns
13              Pessac      63808           Gironde       small towns
14             Talence      42606           Gironde       small towns

11.3 group_by()

The function group_by() is perfect to compute new variables by group.

For example, we can use it to calculate the percentage of amount of a group, as displayed below, creating a new variable “perc” using mutate() :

All_Geo_data |> group_by(Departments) |> 
  mutate(perc = Population / sum(Population)
         )
# A tibble: 14 × 4
# Groups:   Departments [3]
   Cities              Population Departments         perc
   <chr>                    <dbl> <chr>              <dbl>
 1 La Rochelle              74880 Charente Maritime 0.427 
 2 Saintes                  25586 Charente Maritime 0.146 
 3 Rochefort                25183 Charente Maritime 0.144 
 4 Royan                    17875 Charente Maritime 0.102 
 5 Aytre                     8970 Charente Maritime 0.0511
 6 Tonnay-Charente           7739 Charente Maritime 0.0441
 7 Saint-Jean-d’Angely       7702 Charente Maritime 0.0439
 8 Perigny                   7456 Charente Maritime 0.0425
 9 Angouleme                41740 Charente          0.689 
10 Cognac                   18825 Charente          0.311 
11 Bordeaux                254436 Gironde           0.590 
12 Merignac                 70105 Gironde           0.163 
13 Pessac                   63808 Gironde           0.148 
14 Talence                  42606 Gironde           0.0989

We get the % of population that each city represents within one Department.

This is very convenient when we need to define some weights or ratios for example.

11.4 group_by() & summarise()

Here comes the equivalent (in much more powerful way!) of the famous pivot table in Excel.

The functions group_by() and summarise() are used together to aggregate data :

  • group_by() identifies the grouping variable(s).

  • summarise() specifies the aggregation (i.e. the action or calculation to be performed).

The figure below illustrates the concept : we have a data frame with similar values within one dimension, and want to aggregate the values of another (or more) variable around this (or those) dimension.

Figure 41 : the functions group_by() and summarise()

In the example below, we calculate the Total Population by Department, using the function sum() :

All_Geo_data |> group_by(Departments) |>    
    summarise(total_population = sum(Population)
              )
# A tibble: 3 × 2
  Departments       total_population
  <chr>                        <dbl>
1 Charente                     60565
2 Charente Maritime           175391
3 Gironde                     430955

Other useful functions within summarise() are: mean(), median(), sd(), min(), max(), n(), n_distinct().

In the example below we calculate 2 new variables :

  • the Total Population by Department.

  • the number of cities (actually number of occurrences of rows) by Department, using the function n().

All_Geo_data |> group_by(Departments) |>  
    summarise(total_population = sum(Population),
              nb_cities = n()  
              )
# A tibble: 3 × 3
  Departments       total_population nb_cities
  <chr>                        <dbl>     <int>
1 Charente                     60565         2
2 Charente Maritime           175391         8
3 Gironde                     430955         4

11.5 pivot

Data frames can be displayed in 2 ways : wide & long formats

During our data handling, we will need to pivot the data between those 2 formats. For this we can use from the R package tidyr :

  • the functions spread() and gather().

  • or also the functions pivot_longer() and pivot_wider().

Those 2 pairs of functions work the same way.

A wide format displays (spread) several columns (variables), which actually could also be gathered within a single common variable.

For example we can have a data frame in a wide format with different periods of time (January 2025, February 2025, …, December 2025). Those different periods of time could be gathered into one single variable called “period”, creating a longer (then less wide) data frame, as illustrated in the figure below :

Figure 42 : wide &long formats of a data frame

Let’s create a demo data frame, in a long format.

# create vectors
products_family <- c(rep("Family 1", 8), rep("Family 2", 4))

products_description <- c(rep("Product A", 4), rep("Product B", 4), rep("Product C", 4))

period <- c(rep(c("2020/01/01", "2020/02/01", "2020/03/01", "2020/04/01"), 3))

sales <- c(10, 20, 30, 40,
           100, 200, 300, 400,
           5, 10, 15, 20
           )

# combine into one dataframe
initial_data <- data.frame(products_family, 
                           products_description, 
                           period, 
                           sales)

# format the variable period as a date
initial_data$period <- as.Date(initial_data$period, format = "%Y/%m/%d")

# display data frame
initial_data
   products_family products_description     period sales
1         Family 1            Product A 2020-01-01    10
2         Family 1            Product A 2020-02-01    20
3         Family 1            Product A 2020-03-01    30
4         Family 1            Product A 2020-04-01    40
5         Family 1            Product B 2020-01-01   100
6         Family 1            Product B 2020-02-01   200
7         Family 1            Product B 2020-03-01   300
8         Family 1            Product B 2020-04-01   400
9         Family 2            Product C 2020-01-01     5
10        Family 2            Product C 2020-02-01    10
11        Family 2            Product C 2020-03-01    15
12        Family 2            Product C 2020-04-01    20

11.5.1 transform to wide

We can apply the function spread(), to transform a long data frame into a wide one :

Figure 43 : transform a data frame from a long to wide format

Let’s apply the function spread() on the data frame initial_data. We’re going to spread the variable “period” with its different unique values, into new variables :

# spread
df1 <- initial_data |> spread(period, sales)

# display data frame
df1
  products_family products_description 2020-01-01 2020-02-01 2020-03-01
1        Family 1            Product A         10         20         30
2        Family 1            Product B        100        200        300
3        Family 2            Product C          5         10         15
  2020-04-01
1         40
2        400
3         20

We obtain a new data frame with the different periods of time in columns.

11.5.2 transform to long

To perform the opposite transformation, we can apply the function gather(). It transforms a wide data frame into a long one.

Figure 44 : transform a data frame from a wide to a long format

We keep the variables on the left, and we mention the names of the new ones that will be created after pivoting the data: 2 new variables will be created.

The function gather() comes with the function length().

The function length() counts the number of columns of a data frame i.e. its total length.

  • the “:” means from x to y.

  • in the example below, “:” means from the 3rd column until the last column.

Let’s first have a look at the function length() :

length(df1)
[1] 6

It returns the total number of variables of a data frame.

And now let’s apply the function gather() :

# apply gather
df1 <- df1 |> gather(key = "period",  # Name of the new key column
                     value = "sales", # Name of the new value column
                     3:length(df1)
                     )

# display data frame
df1
   products_family products_description     period sales
1         Family 1            Product A 2020-01-01    10
2         Family 1            Product B 2020-01-01   100
3         Family 2            Product C 2020-01-01     5
4         Family 1            Product A 2020-02-01    20
5         Family 1            Product B 2020-02-01   200
6         Family 2            Product C 2020-02-01    10
7         Family 1            Product A 2020-03-01    30
8         Family 1            Product B 2020-03-01   300
9         Family 2            Product C 2020-03-01    15
10        Family 1            Product A 2020-04-01    40
11        Family 1            Product B 2020-04-01   400
12        Family 2            Product C 2020-04-01    20

11.6 joints

There are 4 types of joints : Left Join / Right Join / full join / natural join

The left join is the equivalent of a VLOOKUP in Excel. R provides broader (and easier) options to merge data frames compared to Excel, especially the capacity to perform a full join or a natural join operation.

Figure 45 : 4 types of joints

Let’s illustrate the different types of joints, merging 2 data frames.

We create a first data frame, with 2 variables : Cities and Countries

#----------------------------
# Create the 1st dataframe
#----------------------------

# create 2 vectors
Cities <- c("Shanghai","Tokyo","Sydney","Jakarta")
Countries <- c("China", "Japan", "Australia", "Indonesia")

# combine into 1 dataframe
Geo_data3 <- data.frame(Cities,Countries)

# display data frame
Geo_data3
    Cities Countries
1 Shanghai     China
2    Tokyo     Japan
3   Sydney Australia
4  Jakarta Indonesia

And now a second data frame with 2 other variables : Countries and Regions

#----------------------------
# Create the 2nd dataframe
#----------------------------

# create 2 vectors
Countries <- c("South Korea","China", "New Zealand", "Japan", "Thailand", "Indonesia", "Malaysia", "Germany", "Brazil")

Regions <- c("APAC","APAC", "APAC", "APAC", "APAC", "APAC", "APAC", "EMEA", "LATAM")

# combine into 1 dataframe
Geo_data4 <- data.frame(Countries, Regions)

# display data frame
Geo_data4 
    Countries Regions
1 South Korea    APAC
2       China    APAC
3 New Zealand    APAC
4       Japan    APAC
5    Thailand    APAC
6   Indonesia    APAC
7    Malaysia    APAC
8     Germany    EMEA
9      Brazil   LATAM

Those 2 data frames have a common variable : Countries

Now, let’s illustrate the 4 types of joints.

11.6.1 left join

The function left_join() is the equivalent of the VLOOKUP in Excel :

  • we want to add the value of the variable “Regions” from the table on the right (i.e. Geo_data4) to the existing data frame Geo_data3 (i.e. table on the left).

  • since Australia is not present in the data frame Geo_data4, the Regions appears as “NA”.

# merge
Geo_data5 <- left_join(Geo_data3, Geo_data4)
Joining with `by = join_by(Countries)`
# display data frame
Geo_data5
    Cities Countries Regions
1 Shanghai     China    APAC
2    Tokyo     Japan    APAC
3   Sydney Australia    <NA>
4  Jakarta Indonesia    APAC

11.6.2 right join

The function right_join() performs a similar operation with the previous one, but here we add to the table on the right (i.e. Geo_data4), the related elements of the table on the left (i.e. Geo_data3).

Only 3 Countries (China, Japan, Indonesia) are present in the data frame Geo_data3.

We then get the values of the Cities related to those Countries. For the other Countries, the values are missing and displayed as “NA”.

# merge
Geo_data6 <- right_join(Geo_data3, Geo_data4)
Joining with `by = join_by(Countries)`
# display data frame
Geo_data6
    Cities   Countries Regions
1 Shanghai       China    APAC
2    Tokyo       Japan    APAC
3  Jakarta   Indonesia    APAC
4     <NA> South Korea    APAC
5     <NA> New Zealand    APAC
6     <NA>    Thailand    APAC
7     <NA>    Malaysia    APAC
8     <NA>     Germany    EMEA
9     <NA>      Brazil   LATAM

11.6.3 natural join

This type of merge will keep only the rows that match from both data frames, i.e. the intersection between both.

We use the function merge() and specify the argument all = FALSE.

# merge
Geo_data7 <- merge(Geo_data3, Geo_data4, all = FALSE)

# display data frame
Geo_data7
  Countries   Cities Regions
1     China Shanghai    APAC
2 Indonesia  Jakarta    APAC
3     Japan    Tokyo    APAC

11.6.4 full outer join

This type of merge will keep all the rows from both data frames.

We use the function merge() and specify all = TRUE .

# Merge
Geo_data8 <- merge(Geo_data3, Geo_data4, all = TRUE)

# display data frame
Geo_data8
     Countries   Cities Regions
1    Australia   Sydney    <NA>
2       Brazil     <NA>   LATAM
3        China Shanghai    APAC
4      Germany     <NA>    EMEA
5    Indonesia  Jakarta    APAC
6        Japan    Tokyo    APAC
7     Malaysia     <NA>    APAC
8  New Zealand     <NA>    APAC
9  South Korea     <NA>    APAC
10    Thailand     <NA>    APAC

We fully merged the 2 data frames.

  • all the rows of each data frame are present.

  • the missing values appear as NA.

11.7 cross joint

A particular, and sometimes useful, type of joint is a cross joint. We can realize it using the function crossing() .

In the example below we have 2 data frames without any variables in common. However, we want to merge those 2 data frame. Using the function crossing() we can merge them and get as a result a new data frame with the values of the 2nd data frame repeated along each row of the 1st data frame.

# Example dataframes (reusing df1 and df2 from above)
df1 <- data.frame(code.article = c("A1", "A2", "A3"),
                  demand = c(100, 200, 300))

df2 <- data.frame(period = as.Date(c("2024-01-15", "2024-02-15", "2024-03-15")))

# Perform a cross join using tidyr's crossing function
combined_df <- tidyr::crossing(df1, df2)

# display data frame
combined_df
# A tibble: 9 × 3
  code.article demand period    
  <chr>         <dbl> <date>    
1 A1              100 2024-01-15
2 A1              100 2024-02-15
3 A1              100 2024-03-15
4 A2              200 2024-01-15
5 A2              200 2024-02-15
6 A2              200 2024-03-15
7 A3              300 2024-01-15
8 A3              300 2024-02-15
9 A3              300 2024-03-15

11.8 replace_na()

The previous data frame Geo_data8 has some missing values in the variable “Cities”.

We can replace all the rows with missing values in a variable using the function replace_na().

In the example below, we will replace the missing values (NA) by the value “to be defined”.

# set a working df
df1 <- Geo_data8

# replace NA
df1$Cities <- df1$Cities |> replace_na("to be defined")

# display data frame
df1
     Countries        Cities Regions
1    Australia        Sydney    <NA>
2       Brazil to be defined   LATAM
3        China      Shanghai    APAC
4      Germany to be defined    EMEA
5    Indonesia       Jakarta    APAC
6        Japan         Tokyo    APAC
7     Malaysia to be defined    APAC
8  New Zealand to be defined    APAC
9  South Korea to be defined    APAC
10    Thailand to be defined    APAC

11.9 drop_na()

The previous data frame Geo_data8 has some missing values in the variable “Cities”. We can remove all the rows with missing values in this variable using the function drop_na() :

Geo_data8 |> drop_na(Cities)
  Countries   Cities Regions
1 Australia   Sydney    <NA>
2     China Shanghai    APAC
3 Indonesia  Jakarta    APAC
4     Japan    Tokyo    APAC

11.10 Replace all missing values by zero

Let’s create the following data frame, with some missing values :

# Create a simple dataset
data <- data.frame(
  A = c(1, 2, NA, 4),
  B = c(NA, 2, 3, 4),
  C = c(1, NA, 3, 4)
)


# display data frame
data
   A  B  C
1  1 NA  1
2  2  2 NA
3 NA  3  3
4  4  4  4

Here are a 2 ways to replace all the NA :

# 1st way
data <- data |>
  mutate_all(replace_na, replace = 0)


# 2nd way : a very short one (maybe less “straightforward”)
data[is.na(data)] <- 0