Welcome back to the part two of this training module on data visualization using ggplot2. If this is your first time of seeing this post, please go back to see the previous post where I covered the basic steps of using ggplot2.

In this part I will cover two-dimensional geometries and statistical transformations.

## Two-dimensional geometries

Before we move on, let’s load the tidyverse library with the code below. If you have not installed tidyverse remove the “#” symbol and install it now.

```
# install.packages("tidyverse")
library("tidyverse")
```

```
## ── Attaching packages ────────────────────────────────────────── tidyverse 1.2.1 ──
## ✔ ggplot2 3.0.0 ✔ purrr 0.2.5
## ✔ tibble 1.4.2 ✔ dplyr 0.7.6
## ✔ tidyr 0.8.1 ✔ stringr 1.3.1
## ✔ readr 1.1.1 ✔ forcats 0.3.0
## ── Conflicts ───────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
```

We will still be using our dataset on United States colleges which consists of the school name, city, state, region, highest degree offered by each school, SAT average scores, tution and so on. Once again, I would like to iterate that this dataset is not mine. I found it online and it can be accessed through this link.

Let’s import our data and store it R with the variable name visualData

```
visualData <- read_csv('https://olawaleayilara.github.io/visualData.csv')
```

```
## Parsed with column specification:
## cols(
## id = col_integer(),
## name = col_character(),
## city = col_character(),
## state = col_character(),
## region = col_character(),
## highest_degree = col_character(),
## control = col_character(),
## gender = col_character(),
## admission_rate = col_double(),
## sat_avg = col_integer(),
## undergrads = col_integer(),
## tuition = col_integer(),
## faculty_salary_avg = col_integer(),
## loan_default_rate = col_character(),
## median_debt = col_double(),
## lon = col_double(),
## lat = col_double()
## )
```

```
head(visualData)
```

```
## # A tibble: 6 x 17
## id name city state region highest_degree control gender
## <int> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
## 1 102669 Alas… Anch… AK West Graduate Private CoEd
## 2 101648 Mari… Mari… AL South Associate Public CoEd
## 3 100830 Aubu… Mont… AL South Graduate Public CoEd
## 4 101879 Univ… Flor… AL South Graduate Public CoEd
## 5 100858 Aubu… Aubu… AL South Graduate Public CoEd
## 6 100663 Univ… Birm… AL South Graduate Public CoEd
## # ... with 9 more variables: admission_rate <dbl>, sat_avg <int>,
## # undergrads <int>, tuition <int>, faculty_salary_avg <int>,
## # loan_default_rate <chr>, median_debt <dbl>, lon <dbl>, lat <dbl>
```

Take time to play with the data to ensure it is clean enough before you proceed to visualization. In this case, our data is reasonably clean. Therefore, we can proceed to the next stage.

The first geom that we will be considering is the point geom used for creating scatter plot. **Scatterplot** is a very simple way to display the relationship between two continuous variables. It can also be used to compare categorical variables but there are other variation that are more appropriate for this type of data.

```
ggplot(data=visualData) +
geom_point(mapping=aes(x=tuition, y=faculty_salary_avg))
```

We can add different scale to the aesthetic. For example, let’s add a log scale

```
ggplot(data=visualData) +
geom_point(mapping=aes(x=tuition, y=log(faculty_salary_avg)))
```

**Heatmap of 2d bin counts** this is a useful alternative to point geom in the presence of overplotting. This plot divides the plane into rectangles, counts the number of cases in each rectangle, and then maps the number of cases to the rectnagle’s fill.

```
ggplot(data=visualData) +
geom_bin2d(mapping=aes(x=tuition, y= faculty_salary_avg))
```

**Hexagonal heatmap of 2d bin counts** avoid the visual artefacts sometimes generated by the very regular alignment of geom_bin2d(). This plot divides the plane into regular hexagons, counts the number of cases in each hexagon, and then maps the number of cases to the hexagon fill.

```
ggplot(data=visualData) +
geom_hex(mapping=aes(x=tuition, y= faculty_salary_avg))
```

**Contours of a 2d density estimate** is a useful plot for dealing with overplotting. It perform a 2D kernel density estimation using MASS::kde2d() and display the results with contours.

```
ggplot(data=visualData) +
geom_density2d(mapping=aes(x=tuition, y= faculty_salary_avg))
```

In my next post, I will show how we can modify axes to fix the cut observed in the plot above.

**Smoothed line** aids the eye in seeing patterns in the presence of overplotting.

```
ggplot(data=visualData) +
geom_smooth(mapping=aes(x=tuition, y= faculty_salary_avg))
```

```
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
```

We can as well add a smoothed line to a scatterplot when it is difficult to see the dominant pattern.

```
ggplot(data=visualData,mapping=aes(x=tuition, y= faculty_salary_avg)) +
geom_point() +
geom_smooth()
```

```
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
```

If you’re not interested in the confidence band around the plot, you can turn it off with geom_smooth(se = FALSE).

```
ggplot(data=visualData,mapping=aes(x=tuition, y= faculty_salary_avg)) +
geom_point() +
geom_smooth(se = FALSE)
```

```
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
```

The wiggliness of the line is controlled by the span parameter, which ranges from 0 (exceedingly wiggly) to 1 (not so wiggly).

```
ggplot(data=visualData,mapping=aes(x=tuition, y= faculty_salary_avg)) +
geom_point() +
geom_smooth(span = 0.01)
```

```
## `geom_smooth()` using method = 'gam' and formula 'y ~ s(x, bs = "cs")'
```

An important argument to geom_smooth() is the method, which allows you to select the type of model to use in fitting the smooth curve. Method = “loess”, which is the default for small n, uses a smooth local regression. Loess does not work well for large datasets, an alternative smoothing algorithm is used when n is greater than 1000. Method = “gam” fits a generalised additive model provided by the **mgcv** package. Also, we can fit a linear model by specifying method = “lm”.

```
ggplot(data=visualData,mapping=aes(x=tuition, y= faculty_salary_avg)) +
geom_point() +
geom_smooth(method = "lm")
```

As I mentioned earlier that we can also use the geom_point to compare categorical variables, I will quickly show a plot and highlight the limitation of using geom_point for categorical variables.

```
ggplot(data=visualData) +
geom_point(mapping=aes(x=highest_degree, y=faculty_salary_avg))
```

We observe that it is difficult to see the distribution because many points are plotted in the same location. There are number of ways we can fix this problem. The first approach I will talk about is jittering.

**Jittering** uses geom jitter() to add a little random noise to the data which can help avoid overplotting.

```
ggplot(data=visualData) +
geom_jitter(mapping=aes(x=highest_degree, y=faculty_salary_avg))
```

The second approach is the **Boxplot**, which uses the geom_boxplot() and summarises the shape of the distribution with a handful of summary statistics.

```
ggplot(data=visualData) +
geom_boxplot(mapping=aes(x=highest_degree, y=faculty_salary_avg))
```

And the last one I will show is the **Violin plot**, which show a compact representation of the “density” of the distribution, highlighting the locations where more points are found

```
ggplot(data=visualData) +
geom_violin(mapping=aes(x=highest_degree, y=faculty_salary_avg))
```

Each method has its strengths and weaknesses. Boxplots summarises the the distribution with a five-number summary, while jittered plots show every point but only work with relatively small datasets. Violin plots are very informative and rely majorly on the calculation of a density estimate, which can be tricky to interpret. So, the choice of techniques depends on the researcher.

Sometimes the best visual to show a trend is the **line plot**. This is often the case when you are using a time series data. To plot this graph, you can use the geom_line or geom_path function. We will use the economics dataset in R.

```
ggplot(data=economics) +
geom_line(mapping=aes(x=date, y=uempmed))
```

```
ggplot(data=economics,mapping=aes(x=date, y=uempmed)) +
geom_path()
```

At this point, I have covered almost all the two-dimensional geometries, but before we move forward I will like to briefly show you how to use **group** in our aesthetic. In some cases, it might be reasonable to compare data among different groups and still present them with the same visual. This is common in longitudinal studies with many subjects, where the plots are often descriptively called **spaghetti plots**. For this example, let us use a simple longitudinal dataset, *Oxboys* , from the **nlme** package. The data records the heights (height) and centered ages (age) of 26 boys (Subject), measured on nine occasions (Occasion).

```
data(Oxboys, package = "nlme")
head(Oxboys)
```

```
## Grouped Data: height ~ age | Subject
## Subject age height Occasion
## 1 1 -1.0000 140.5 1
## 2 1 -0.7479 143.4 2
## 3 1 -0.4630 144.8 3
## 4 1 -0.1643 147.1 4
## 5 1 -0.0027 147.7 5
## 6 1 0.2466 150.2 6
```

```
ggplot(data = Oxboys, mapping = aes(age, height, group = Subject)) +
geom_point() +
geom_line()
```

The plot above show the growth trajectory for each boy (i.e each subject). Suppose we want to add a single smooth line, showing the overall trend for all boys.

```
data(Oxboys, package = "nlme")
ggplot(data = Oxboys, mapping = aes(age, height, group = Subject)) +
geom_line() +
geom_smooth(method = "lm", se = FALSE)
```

Ooops, this is not what we want to achieve, we have just succeeded in adding a smoothed line for each boy. To get to our goal, let’s not specify the grouping aesthetic in ggplot(), where it will be applied to all layers, but instead we specify it in geom_line() so it applies only to the lines.

```
data(Oxboys, package = "nlme")
ggplot(data = Oxboys, mapping = aes(age, height)) +
geom_line(mapping = aes(group = Subject)) +
geom_smooth(method = "lm", se = FALSE)
```

In some cases, the plots may have a discrete x scale, but you still want to draw lines connecting across groups. For example, let’s draw boxplots of height at each measurement occasion

```
data(Oxboys, package = "nlme")
ggplot(data = Oxboys, mapping = aes(Occasion, height)) +
geom_boxplot()
```

To overlay lines that connect each individual, just adding geom_line() will not work. This is because lines are drawn within each occasion, not across each subject

```
data(Oxboys, package = "nlme")
ggplot(data = Oxboys, mapping = aes(Occasion, height)) +
geom_boxplot() +
geom_line(colour = "red", alpha = 0.5)
```

In order to achieve what we want, we need to override the grouping showing that we want one line per boy

```
data(Oxboys, package = "nlme")
ggplot(data = Oxboys, mapping = aes(Occasion, height)) +
geom_boxplot() +
geom_line(mapping = aes(group = Subject), colour = "red", alpha = 0.5)
```

## Statistical transformations

A statistical transformation, or stat, transforms the data, usually by summarising it in some manner. A useful example of stat is the smoother, which calculates the smoothed mean of response variable (y), conditional on explanatory variable (x). Technically, we’ve already used many of ggplot2’s stats because they’re used behind the scenes to generate many important geoms such as:

- stat_bin(): geom_bar(), geom_freqpoly(), geom_histogram()
- stat_bin2d(): geom_bin2d()
- stat_bindot(): geom_dotplot()
- stat_binhex(): geom_hex()
- stat_boxplot(): geom_boxplot()
- stat_contour(): geom_contour()
- stat_quantile(): geom_quantile()
- stat_smooth(): geom_smooth()
- stat_sum(): geom_count()

There are some other statistical transformations that can’t be created with a geom function. For example,

- stat_ecdf(): compute a empirical cumulative distribution plot.
- stat_function(): compute y values from a function of x values.
- stat_summary(): summarise y values at distinct x values.
- stat_summary2d(), stat summary hex(): summarise binned values.
- stat_qq(): perform calculations for a quantile-quantile plot.
- stat_spoke(): convert angle and radius to position.
- stat_unique(): remove duplicated rows.

Two ways to use the **stat** function:

- Add a stat_() function and override the default geom

```
ggplot(data=visualData,mapping=aes(x=highest_degree, y=faculty_salary_avg)) +
geom_point() +
stat_summary(geom = "point", fun.y = "mean", colour = "red", size = 4)
```

- Add a geom_() function and override the default stat:

```
ggplot(data=visualData,mapping=aes(x=highest_degree, y=faculty_salary_avg)) +
geom_point() +
geom_point(stat = "summary", fun.y = "mean", colour = "red", size = 4)
```

We can as well look at the empirical cumulative distribution plot of the average salary of faculty members in our data using

```
ggplot(data=visualData) +
stat_ecdf(mapping = aes(faculty_salary_avg))
```

Let’s explore the stat_summary function more to produce some interesting visuals.

```
ggplot(data=visualData,mapping=aes(x=highest_degree, y=faculty_salary_avg)) +
stat_summary()
```

```
## No summary function supplied, defaulting to `mean_se()
```

This is the default function for stat_summary and it returned both the mean and standard error estimates. We can change the geom to produce some other nice plots

```
ggplot(data=visualData,mapping=aes(x=highest_degree, y=faculty_salary_avg)) +
stat_summary(geom = "crossbar")
```

```
## No summary function supplied, defaulting to `mean_se()
```

```
ggplot(data=visualData,mapping=aes(x=highest_degree, y=faculty_salary_avg)) +
stat_summary(geom = "errorbar")
```

```
## No summary function supplied, defaulting to `mean_se()
```

```
ggplot(data=visualData,mapping=aes(x=highest_degree, y=faculty_salary_avg)) +
stat_summary(geom = "linerange")
```

```
## No summary function supplied, defaulting to `mean_se()
```

```
ggplot(data=visualData,mapping=aes(x=highest_degree, y=faculty_salary_avg)) +
stat_summary(geom = "pointrange")
```

```
## No summary function supplied, defaulting to `mean_se()
```

I will wrap up this session by showing how you can specify fun.y, fun.ymin and fun.ymax with any function. For example, Mean ± SD. Note: You can write the function outside ggplot and provide the stored variable in the fun.y, fun.ymin and fun.ymax.

```
ggplot(data=visualData,mapping=aes(x=highest_degree, y=faculty_salary_avg)) +
stat_summary(fun.y = mean,
fun.ymax = function(x) mean(x) + sd(x),
fun.ymin = function(x) mean(x) - sd(x),
geom = "pointrange")
```

Understanding the grammar of ggplot2, and how its components fit together, allows you to create a wider range of visualizations, combine multiple sources of data, and customise to your heart’s content. I will leave you with the plot below, where I just combined different stuffs to get something.

```
ggplot(data=visualData,mapping=aes(x=highest_degree, y=faculty_salary_avg)) +
stat_summary(fun.y = mean,
fun.ymax = function(x) max(x),
fun.ymin = function(x) min(x),
geom = "pointrange") +
geom_jitter() +
geom_violin() +
stat_summary(geom = "point", fun.y = "mean", colour = "red", size = 4)
```

In this post, I have covered the basics of two-dimensional geometries. In my next post, I will cover scales, axes, legends, positioning and themes. Also, I will briefly introduce how to program with ggplot2. Until then, you can go ahead and pick different variables to produce the plots we have covered in this session.

**Reference**

- Wickham H. (2016). ggplot2: Elegant Graphics for Data Analysis (Use R!), 2nd Edition. Springer, New York

For any question and contributions, please feel free to email ayilarof@myumanitoba.ca