30 Python Pandas training exercises solved in R Tidyverse
In the field of data science, both Python and R have their specific use cases. If there was ever any greater divide in the past, today both languages are slowly growing closer to each other. Even the IDE RStudio, widely used among data scientists working with R, has integrated Python into its environment, going as far as calling this step “A Data Science Love Story.”
While most people would argue for any data scientist to have at least a basic understanding of the other language, there are in fact too few training exercises out there that could help a beginner grasp both languages similarities, but also the apparent differences between R and Python.
To improve this situation at least a tiny bit, I have solved 30 out of 60 already existing Pandas exercises in R Tidyverse and R, so that one could tackle the training exercises side by side when preferred. Since I feel, at least until now, feel more familiar with Pandas than R Tidyverse, I am sure there my code still leaves room for improvement.
If you like to find out more about the differences and similarities between Pandas and R, I recommend checking out the Pandas Quick Reference and the article Anything you can do, I can do (kinda). Tidyverse pipes in Pandas by Steven Morse.
The easiest way to go through these exercises is solving the Pandas exercises inside an R notebook first and then comparing your solutions with the code provided here.
In case you find any mistakes or other, perhaps more efficient ways to solve any of these problems, please simply open an issue on Github.
Importing R Tidyverse
Getting started and checking your R Tidyverse setup
Difficulty: easy
1. Import the tidyverse package.
library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.0 --
## v ggplot2 3.3.3 v purrr 0.3.4
## v tibble 3.1.0 v dplyr 1.0.5
## v tidyr 1.1.3 v stringr 1.4.0
## v readr 1.4.0 v forcats 0.5.1
## Warning: Paket 'tibble' wurde unter R Version 4.0.4 erstellt
## Warning: Paket 'tidyr' wurde unter R Version 4.0.4 erstellt
## Warning: Paket 'dplyr' wurde unter R Version 4.0.4 erstellt
## Warning: Paket 'forcats' wurde unter R Version 4.0.4 erstellt
## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::filter() masks stats::filter()
## x dplyr::lag() masks stats::lag()
2. Print the version of tidyverse that has been imported.
packageVersion("tidyverse")
## [1] '1.3.0'
3. Print out all the version information of the libraries that are included with the tidyverse library.
sessionInfo()
## R version 4.0.3 (2020-10-10)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 19041)
##
## Matrix products: default
##
## locale:
## [1] LC_COLLATE=German_Germany.1252 LC_CTYPE=German_Germany.1252
## [3] LC_MONETARY=German_Germany.1252 LC_NUMERIC=C
## [5] LC_TIME=German_Germany.1252
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] forcats_0.5.1 stringr_1.4.0 dplyr_1.0.5 purrr_0.3.4
## [5] readr_1.4.0 tidyr_1.1.3 tibble_3.1.0 ggplot2_3.3.3
## [9] tidyverse_1.3.0
##
## loaded via a namespace (and not attached):
## [1] tidyselect_1.1.0 xfun_0.22 bslib_0.2.4 haven_2.3.1
## [5] colorspace_2.0-0 vctrs_0.3.7 generics_0.1.0 htmltools_0.5.1.1
## [9] yaml_2.2.1 utf8_1.2.1 rlang_0.4.10 jquerylib_0.1.3
## [13] pillar_1.5.1 withr_2.4.1 glue_1.4.2 DBI_1.1.1
## [17] dbplyr_2.1.0 modelr_0.1.8 readxl_1.3.1 lifecycle_1.0.0
## [21] munsell_0.5.0 blogdown_1.2 gtable_0.3.0 cellranger_1.1.0
## [25] rvest_1.0.0 evaluate_0.14 knitr_1.31 ps_1.6.0
## [29] fansi_0.4.2 broom_0.7.5 Rcpp_1.0.6 backports_1.2.1
## [33] scales_1.1.1 jsonlite_1.7.2 fs_1.5.0 hms_1.0.0
## [37] digest_0.6.27 stringi_1.5.3 bookdown_0.21 grid_4.0.3
## [41] cli_2.3.1 tools_4.0.3 magrittr_2.0.1 sass_0.3.1
## [45] crayon_1.4.1 pkgconfig_2.0.3 ellipsis_0.3.1 xml2_1.3.2
## [49] reprex_2.0.0 lubridate_1.7.10 rstudioapi_0.13 assertthat_0.2.1
## [53] rmarkdown_2.7 httr_1.4.2 R6_2.5.0 compiler_4.0.3
DataFrame basics
A few of the fundamental routines for selecting, sorting, adding and aggregating data in DataFrames
Difficulty: easy
Consider the following Python dictionary data
and Python list labels
:
data = {'animal': ['cat', 'cat', 'snake', 'dog', 'dog', 'cat', 'snake', 'cat', 'dog', 'dog'],
'age': [2.5, 3, 0.5, np.nan, 5, 2, 4.5, np.nan, 7, 3],
'visits': [1, 3, 2, 3, 2, 3, 1, 1, 2, 1],
'priority': ['yes', 'yes', 'no', 'yes', 'no', 'no', 'no', 'yes', 'no', 'no']}
labels = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j']
(This is just some meaningless data I made up with the theme of animals and trips to a vet.)
4. Create an R tibble or dataframe called df
from the (Python) data
which has the index labels
.
animal <-
c('cat',
'cat',
'snake',
'dog',
'dog',
'cat',
'snake',
'cat',
'dog',
'dog')
age <- c(2.5, 3, 0.5, NA, 5, 2, 4.5, NA, 7, 3)
visits <- c(1, 3, 2, 3, 2, 3, 1, 1, 2, 1)
priority <-
c('yes', 'yes', 'no', 'yes', 'no', 'no', 'no', 'yes', 'no', 'no')
df <- data.frame(age, animal, priority, visits)
row.names(df) <- c('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j')
df <- as_tibble(df)
str(df)
## tibble[,4] [10 x 4] (S3: tbl_df/tbl/data.frame)
## $ age : num [1:10] 2.5 3 0.5 NA 5 2 4.5 NA 7 3
## $ animal : chr [1:10] "cat" "cat" "snake" "dog" ...
## $ priority: chr [1:10] "yes" "yes" "no" "yes" ...
## $ visits : num [1:10] 1 3 2 3 2 3 1 1 2 1
5. Display a summary of the basic information about this DataFrame and its data (hint: there is a single method that can be called on the DataFrame).
summary(df)
## age animal priority visits
## Min. :0.500 Length:10 Length:10 Min. :1.00
## 1st Qu.:2.375 Class :character Class :character 1st Qu.:1.00
## Median :3.000 Mode :character Mode :character Median :2.00
## Mean :3.438 Mean :1.90
## 3rd Qu.:4.625 3rd Qu.:2.75
## Max. :7.000 Max. :3.00
## NA's :2
#glimpse(df)
#dim(df)
#str(df)
#print(df, quote = TRUE, row.names = FALSE)
6. Return the first 3 rows of the DataFrame df
.
head(df,3)
## # A tibble: 3 x 4
## age animal priority visits
## <dbl> <chr> <chr> <dbl>
## 1 2.5 cat yes 1
## 2 3 cat yes 3
## 3 0.5 snake no 2
7. Select just the ‘animal’ and ‘age’ columns from the DataFrame df
.
df %>%
select(animal, age)
## # A tibble: 10 x 2
## animal age
## <chr> <dbl>
## 1 cat 2.5
## 2 cat 3
## 3 snake 0.5
## 4 dog NA
## 5 dog 5
## 6 cat 2
## 7 snake 4.5
## 8 cat NA
## 9 dog 7
## 10 dog 3
8. Select the data in rows [3, 4, 8]
and in columns ['animal', 'age']
.
df %>%
select(animal, age) %>%
slice(3, 4, 8)
## # A tibble: 3 x 2
## animal age
## <chr> <dbl>
## 1 snake 0.5
## 2 dog NA
## 3 cat NA
9. Select only the rows where the number of visits is greater than 3.
df %>%
filter(visits > 3)
## # A tibble: 0 x 4
## # ... with 4 variables: age <dbl>, animal <chr>, priority <chr>, visits <dbl>
10. Select the rows where the age is missing, i.e. it is NaN
.
df[rowSums(is.na(df)) > 0,]
## # A tibble: 2 x 4
## age animal priority visits
## <dbl> <chr> <chr> <dbl>
## 1 NA dog yes 3
## 2 NA cat yes 1
11. Select the rows where the animal is a cat and the age is less than 3.
df %>%
select(age,animal)
## # A tibble: 10 x 2
## age animal
## <dbl> <chr>
## 1 2.5 cat
## 2 3 cat
## 3 0.5 snake
## 4 NA dog
## 5 5 dog
## 6 2 cat
## 7 4.5 snake
## 8 NA cat
## 9 7 dog
## 10 3 dog
filter(df, animal == "cat") %>%
filter(age < 3)
## # A tibble: 2 x 4
## age animal priority visits
## <dbl> <chr> <chr> <dbl>
## 1 2.5 cat yes 1
## 2 2 cat no 3
12.** Select the rows the age is between 2 and 4 (inclusive).
df %>%
filter(1 < age & age < 5)
## # A tibble: 5 x 4
## age animal priority visits
## <dbl> <chr> <chr> <dbl>
## 1 2.5 cat yes 1
## 2 3 cat yes 3
## 3 2 cat no 3
## 4 4.5 snake no 1
## 5 3 dog no 1
13. Change the age in row ‘f’ to 1.5.
df[6, 1] = 1.5 #need to check whether row names were included
14. Calculate the sum of all visits in df
(i.e. find the total number of visits).
df %>%
select(visits) #integer column needs to be selected first
## # A tibble: 10 x 1
## visits
## <dbl>
## 1 1
## 2 3
## 3 2
## 4 3
## 5 2
## 6 3
## 7 1
## 8 1
## 9 2
## 10 1
sum(visits)
## [1] 19
15. Calculate the mean age for each different animal in df
.
df %>%
group_by(animal) %>%
summarise(m = mean(age)) #NAs are not ignored
## # A tibble: 3 x 2
## animal m
## <chr> <dbl>
## 1 cat NA
## 2 dog NA
## 3 snake 2.5
16. Append a new row ‘k’ to df
with your choice of values for each column. Then delete that row to return the original DataFrame.
k <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
with_and_without_k <- df %>%
add_column(k)
# and then deleting the new row...
with_and_without_k %>%
select(-one_of("k"))
## # A tibble: 10 x 4
## age animal priority visits
## <dbl> <chr> <chr> <dbl>
## 1 2.5 cat yes 1
## 2 3 cat yes 3
## 3 0.5 snake no 2
## 4 NA dog yes 3
## 5 5 dog no 2
## 6 1.5 cat no 3
## 7 4.5 snake no 1
## 8 NA cat yes 1
## 9 7 dog no 2
## 10 3 dog no 1
17. Count the number of each type of animal in df
.
df %>%
count(animal)
## # A tibble: 3 x 2
## animal n
## <chr> <int>
## 1 cat 4
## 2 dog 4
## 3 snake 2
18. Sort df
first by the values in the ‘age’ in decending order, then by the value in the ‘visits’ column in ascending order (so row i
should be first, and row d
should be last).
df %>%
arrange(desc(age), visits)
## # A tibble: 10 x 4
## age animal priority visits
## <dbl> <chr> <chr> <dbl>
## 1 7 dog no 2
## 2 5 dog no 2
## 3 4.5 snake no 1
## 4 3 dog no 1
## 5 3 cat yes 3
## 6 2.5 cat yes 1
## 7 1.5 cat no 3
## 8 0.5 snake no 2
## 9 NA cat yes 1
## 10 NA dog yes 3
19. The ‘priority’ column contains the values ‘yes’ and ‘no’. Replace this column with a column of boolean values: ‘yes’ should be True
and ‘no’ should be False
.
df %>%
mutate(boolean_values = case_when(priority == 'yes' ~ 1,
priority == 'no' ~ 0))
## # A tibble: 10 x 5
## age animal priority visits boolean_values
## <dbl> <chr> <chr> <dbl> <dbl>
## 1 2.5 cat yes 1 1
## 2 3 cat yes 3 1
## 3 0.5 snake no 2 0
## 4 NA dog yes 3 1
## 5 5 dog no 2 0
## 6 1.5 cat no 3 0
## 7 4.5 snake no 1 0
## 8 NA cat yes 1 1
## 9 7 dog no 2 0
## 10 3 dog no 1 0
20. In the ‘animal’ column, change the ‘snake’ entries to ‘python’.
df %>%
select(animal)
## # A tibble: 10 x 1
## animal
## <chr>
## 1 cat
## 2 cat
## 3 snake
## 4 dog
## 5 dog
## 6 cat
## 7 snake
## 8 cat
## 9 dog
## 10 dog
recode(animal, snake = "python")
## [1] "cat" "cat" "python" "dog" "dog" "cat" "python" "cat"
## [9] "dog" "dog"
21. For each animal type and each number of visits, find the mean age. In other words, each row is an animal, each column is a number of visits and the values are the mean ages (hint: use a pivot table).
df %>%
group_by(animal) %>%
summarise(mean = mean(visits), n = n())
## # A tibble: 3 x 3
## animal mean n
## <chr> <dbl> <int>
## 1 cat 2 4
## 2 dog 2 4
## 3 snake 1.5 2
DataFrames: beyond the basics
Slightly trickier: you may need to combine two or more methods to get the right answer
Difficulty: medium
The previous section was tour through some basic but essential DataFrame operations. Below are some ways that you might need to cut your data, but for which there is no single “out of the box” method.
22. You have a DataFrame df
with a column ‘A’ of integers. For example:
df = pd.DataFrame({'A': [1, 2, 2, 3, 4, 5, 5, 5, 6, 7, 7]})
library(tidyverse)
A <- c(1, 2, 2, 3, 4, 5, 5, 5, 6, 7, 7)
df <- data.frame(A)
df <- as_tibble(df)
How do you filter out rows which contain the same integer as the row immediately above?
You should be left with a column containing the following values:
1, 2, 3, 4, 5, 6, 7
df %>%
distinct()
## # A tibble: 7 x 1
## A
## <dbl>
## 1 1
## 2 2
## 3 3
## 4 4
## 5 5
## 6 6
## 7 7
23. Create a dataframe df with 5x3 of random numeric values
df = pd.DataFrame(np.random.random(size=(5, 3))) # this is a 5x3 DataFrame of float values
df = data.frame(replicate(5,runif(3, min=5, max=10),3))
How do you subtract the row mean from each element in the row?
df_minus_row_mean <- sweep(df, MARGIN=1, STATS= rowMeans(df))
df_minus_row_mean
## X1 X2 X3 X4 X5
## 1 0.4120193 -0.9561412 2.492796 -1.291351 -0.6573229
## 2 0.5441137 -1.4116169 -1.329254 1.595746 0.6010108
## 3 0.6777554 1.1990000 -1.104223 -1.476673 0.7041405
24. Suppose you have DataFrame with 10 columns of real numbers, for example:
df = data.frame(replicate(10,runif(10, min=5, max=10),3))
df = data.frame(replicate(10,runif(10, min=5, max=10),3))
Which column of numbers has the smallest sum? Return that column’s label.
sort(colSums(df), decreasing = FALSE)[1]
## X4
## 71.03754
25. How do you count how many unique rows a DataFrame has (i.e. ignore all rows that are duplicates)? As input, use a DataFrame of zeros and ones with 10 rows and 3 columns.
df = data.frame(replicate(3,sample(0:1,10,rep=TRUE)))
df %>%
distinct() %>%
nrow()
## [1] 6
The next three puzzles are slightly harder.
26. In the cell below, you have a DataFrame df
that consists of 10 columns of floating-point numbers. Exactly 5 entries in each row are NaN values.
For each row of the DataFrame, find the column which contains the third NaN value.
You should return a Series of column labels: e, c, d, h, d
nan = np.nan
data = [[0.04, nan, nan, 0.25, nan, 0.43, 0.71, 0.51, nan, nan],
[ nan, nan, nan, 0.04, 0.76, nan, nan, 0.67, 0.76, 0.16],
[ nan, nan, 0.5 , nan, 0.31, 0.4 , nan, nan, 0.24, 0.01],
[0.49, nan, nan, 0.62, 0.73, 0.26, 0.85, nan, nan, nan],
[ nan, nan, 0.41, nan, 0.05, nan, 0.61, nan, 0.48, 0.68]]
columns = list('abcdefghij')
df = pd.DataFrame(data, columns=columns)
# write a solution to the question here
library(tidyverse)
library(data.table)
## Warning: Paket 'data.table' wurde unter R Version 4.0.4 erstellt
##
## Attache Paket: 'data.table'
## The following objects are masked from 'package:dplyr':
##
## between, first, last
## The following object is masked from 'package:purrr':
##
## transpose
data = list(
list(0.04, NaN, NaN, 0.25, NaN, 0.43, 0.71, 0.51, NaN, NaN),
list(NaN, NaN, NaN, 0.04, 0.76, NaN, NaN, 0.67, 0.76, 0.16),
list(NaN, NaN, 0.5 , NaN, 0.31, 0.4 , NaN, NaN, 0.24, 0.01),
list(0.49, NaN, NaN, 0.62, 0.73, 0.26, 0.85, NaN, NaN, NaN),
list(NaN, NaN, 0.41, NaN, 0.05, NaN, 0.61, NaN, 0.48, 0.68)
)
df = data.table::rbindlist(data)
letters_to_split = c(strsplit("abcdefghij", ""))
character_vectors_for_columns = unlist(letters_to_split)
colnames(df) <- character_vectors_for_columns
For each row of the DataFrame, find the column which contains the third NaN value.
subscript <- apply(df, MARGIN =1, FUN = function(x) which(is.na(x))[3])
colnames(df)[subscript]
## [1] "e" "c" "d" "h" "d"
27. A DataFrame has a column of groups ‘grps’ and and column of integer values ‘vals’:
splitted_list = strsplit('aaabbcaabcccbbc', "")
vals = c(12,345,3,1,45,14,4,52,54,23,235,21,57,3,87)
df <- data.frame(splitted_list, vals)
colnames(df) <- c('grps', 'vals')
For each group, find the sum of the three greatest values. You should end up with the answer as follows:
grps
a 409
b 156
c 345
df %>%
group_by(grps) %>%
summarise(sum= sum(vals))
## # A tibble: 3 x 2
## grps sum
## <chr> <dbl>
## 1 a 416
## 2 b 160
## 3 c 380
28. The DataFrame df
constructed below has two integer columns ‘A’ and ‘B’. The values in ‘A’ are between 1 and 100 (inclusive).
For each group of 10 consecutive integers in ‘A’ (i.e. (0, 10]
, (10, 20]
, …), calculate the sum of the corresponding values in column ‘B’.
The answer should be a Series as follows:
A
(0, 10] 635
(10, 20] 360
(20, 30] 315
(30, 40] 306
(40, 50] 750
(50, 60] 284
(60, 70] 424
(70, 80] 526
(80, 90] 835
(90, 100] 852
df = data.frame(replicate(2,sample(0:100,100,rep=TRUE)))
x <- c("A", "B")
colnames(df) <- x
# write a solution to the question here
sum_binned_columns = df %>%
mutate(bin = cut(A, breaks = c(Inf, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100))) %>%
group_by(bin) %>%
summarise(sum = sum(B), n = n())
sum_binned_columns
## # A tibble: 11 x 3
## bin sum n
## <fct> <int> <int>
## 1 (0,10] 527 12
## 2 (10,20] 256 7
## 3 (20,30] 282 4
## 4 (30,40] 861 16
## 5 (40,50] 676 14
## 6 (50,60] 603 12
## 7 (60,70] 457 8
## 8 (70,80] 906 16
## 9 (80,90] 307 5
## 10 (90,100] 239 5
## 11 <NA> 38 1
DataFrames: harder problems
These might require a bit of thinking outside the box…
…but all are solvable using just the usual pandas/NumPy methods (and so avoid using explicit for
loops).
Difficulty: hard
29. Consider a DataFrame df
where there is an integer column ‘X’:
df = pd.DataFrame({'X': [7, 2, 0, 3, 4, 2, 5, 0, 3, 4]})
For each value, count the difference back to the previous zero (or the start of the Series, whichever is closer). These values should therefore be
[1, 2, 0, 1, 2, 3, 4, 0, 1, 2]
Make this a new column ‘Y’
library(tidyverse)
integers = c(7, 2, 0, 3, 4, 2, 5, 0, 3, 4)
l = c(7, 2, 0, 3, 4, 2, 5, 0, 3, 4)
i = 0
`%+=%` = function(e1,e2) eval.parent(substitute(e1 <- e1 + e2))
for (element in l) {
if (element != 0) {
i %+=% 1
}
else {
i = 0
}
vector <- c(vector, i)
}
30. Consider the DataFrame constructed below which contains rows and columns of numerical data.
Create a list of the column-row index locations of the 3 largest values in this DataFrame. In this case, the answer should be:
list(c(5, 7), c(6, 4), c(2, 5))
df = data.frame(replicate(8,sample(1:101,8,rep=TRUE)))
lst = c(tail(sort(unlist(df, use.names = FALSE)), 3))
for (element in lst) {
print(element)
print(which(df==element, arr.ind=TRUE))
}
## [1] 98
## row col
## [1,] 8 8
## [1] 99
## row col
## [1,] 1 7
## [1] 101
## row col
## [1,] 3 8
You can download the whole script on my Github.