Plotting typological data with R

I made a tutorial on how to plot typological data (here from grambank) with R.

Edit: I redid the tutorial to reflect a more sustainable workflow, using the data as published on zenodo rather than the download link on grambank. I also added part 2 on how to combine two data sets.

Thanks go to Robert Forkel and Hedvig SkirgÄrd, who were very helpful in pointing me to the right places and resources. Shout-out to Simon Greenhill for creating the rcldf package.

Here is the code.

# For any package on cran that you have not installed yet, 
# RStudio will probably ask you whether you want to install.
# Otherwise, install the package with the command install.packages("PACKAGE"), 
# do not forget the quotation marks. Example:
# install.packages("sf")
library(ggplot2)
library(dplyr)
library(sf)
library(rnaturalearth)
# the rnaturalearthhires package is not on cran,
# because it is too big. Uncomment to download package
 install.packages(
  "rnaturalearthhires",
  repos = "https://ropensci.r-universe.dev",
  type = "source"
)
library(rnaturalearthhires)

# The rcldf package is also not on cran, so you have to install it with devtools.
# Uncomment the following lines to install package.
# library(devtools)
# devtools::install_github("SimonGreenhill/rcldf", dependencies = TRUE)

library(rcldf)

# Check out the documentation here for exploring the rcldf package:
# https://github.com/grambank/grambank/wiki/Fetching-and-analysing-Grambank-data-with-R
# The following section is mostly taken from there.

grambank <-  rcldf::cldf(mdpath = "https://zenodo.org/records/7740140/files/grambank/grambank-v1.0.zip")
# what tables are there?
summary(grambank)

# get languages:
head(grambank$tables$LanguageTable)
# get values:
head(grambank$tables$ValueTable)

# the package also has a command to join various tables into one big table, 
# so we can look up parameter values and coordinates in one data frame.
gb.wide <- as.cldf.wide(grambank, 'ValueTable')
head(gb.wide)

# Now we proceed with making our table for numeral classifiers in Cameroon
# (or the parameter and region of your choice)

# First, we create or map data for Cameroon:
cameroon <- ne_states(country = "Cameroon", returnclass = "sf")

# Now, we want to filter the grambank data. 
# We only want language coordinates within Cameroon.

# We first need to figure out the map meta data for our country map.
st_crs(cameroon)
# The crucial part of the output here is this:
# Coordinate Reference System:
# User input: WGS 84 
# See here for further reference:
# https://earthdatascience.org/courses/earth-analytics/spatial-data-r/understand-epsg-wkt-and-other-crs-definition-file-types/

# We can save this parameter like so:
projcrs <- "+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0"
gbsf <- st_as_sf(x = gb.wide,                         
                      coords = c("Longitude", "Latitude"),
                      crs = projcrs, na.fail = FALSE)
# now we can filter our grambank table for those
# languages that are spoken in Cameroon
camergb <-  st_filter(gbsf, cameroon)
head(camergb)

# Next, we filter for the parameter we want to look at.
# In our example, that's numeral classifiers.
# We can look up its code at  https://grambank.clld.org/parameters
# The parameter for numeral classifiers is GB057.
camergb.clf <- filter(camergb, Parameter_ID == "GB057")

camergb.clf
# Here, we plot the Grambank data to a Map
ggplot() +
  geom_sf(data=cameroon, fill="#eeeeee")+
  geom_sf(data=camergb.clf, aes(fill=as.factor(Value)), shape=21)+
  scale_fill_manual(values=c("#ffaa99"), labels=c("absent", "unkown")) +
  labs(fill="Classifiers")+
  theme_void()


############################################
#     Part 2: Combine with second dataset  #
############################################

#Classifiers from Her et al.
heretal <- read.csv("https://raw.githubusercontent.com/cldf-datasets/wacl/refs/heads/main/raw/WACL_v1.csv")
head(heretal)
# Replace values to make them compatible with grambank values
heretal$CLF[heretal$CLF == FALSE] <- 0
heretal$CLF[heretal$CLF == TRUE] <- 1
heretal

# Transform data frame to shape file object
heretalSF <- st_as_sf(x = heretal,                         
                      coords = c("longitude", "latitude"),
                      crs = projcrs, na.fail = FALSE)

# Filter for Cameroonian languages
camerher <- st_filter(heretalSF, cameroon)
head(camerher)

# Combine dataframes camerher and camergb.clf into camerclf
# First, replace column name "Value" by "CLF" in the grambank data
# to conform with the other study
camergb.clf <- camergb.clf %>% rename_at('Value', ~'CLF')
# replace value "?" by "3" in grambank data.
# this will create a nicer order of categories later
camergb.clf <- camergb.clf |> mutate(CLF = ifelse(is.na(CLF), 3, CLF))
# currently, the grambank value is stored as a character string
# <chr>, because it originally contained "?"
# We want to store it as a number instead, for nicer automatic ordering
camergb.clf$CLF <- sapply(camergb.clf$CLF, as.double)
# Check if everything is fine.
View(camergb.clf)
# Add the camerher rows under the grambank rows. 
# create an extra column "id" to index the data set
# id= 1 --> grambank, id = 2 --> Her et al.
camerclf <- bind_rows(list(camergb.clf, camerher), .id = "id")
camerclf

# Combine everything into one plot
ggplot() +
  geom_sf(data=cameroon, fill="#eeeeee")+
  geom_sf(data=camerclf, aes(shape=as.factor(id), fill=as.factor(CLF),  size=as.factor(id)))+
  scale_shape_manual(values=c(23,21), labels=c("Grambank", "Her et al. (2022)"))+
  scale_fill_manual(values=c("#ffaa99", "black", "white"), labels=c("absent", "present", "unkown")) +
  guides(fill=guide_legend(override.aes = list(shape=21)))+
  scale_size_manual(values=c(3,1.5), labels=c("Grambank", "Her et al. (2022)"))+
  labs(fill="Classifiers", shape="Dataset", size="Dataset")+
  theme_void()

Custom typological maps with R

I used to plot my typological language data to geo-spatial maps with Generic Mapping Tools, which is awesome, and where a simple two-liner will do the job. But I found this hard to use in teaching, since it doesn’t run smoothly on everyone’s operating system. So it’s time for me to move on and learn to do maps with R. There are a few awesome resources out there, including lingtypology, which reads data directly off glottolog.

But I wanted to plot data that is not included in a database, and since it’s actually easy, but not entirely trivial to find on the internet so far, here’s a short tutorial. First of all, here is our little data set, with geo-spatial coordinates for each language, language family and basic word order info. Save this to a text file in your working directory with the name “typology.txt”.

Language,Latitude,Longitude,Family,Family2,Word order
Movima,-13.81,-65.63,Isolate,9,0
Arapaho,43.39,-108.81,Algic,10,OV
Alta,15.69,121.45,Austronesian,12,OV
Savosavo,-9.13,159.81,Isolate,9,OV
Teop,-5.67,154.97,Austronesian,11,VO
Sumi,26,94.42,Sino-Tibetan,12,VO
Yali,-4.08,139.46,Nuclear-Trans-New-Guinea,13,OV
Beja,17.24,36.67,Afro-Asiatic,14,VO
Vera'a,-13.89,167.43,Austronesian,11,OV
Cabecar,9.67,-83.41,Chibchan,15,VO
Urum,42.04,43.99,Turkic,16,OV
Dolgan,71.11,94.29,Turkic,16,OV
Gorwaa,-4.24,35.8,Afro-Asiatic,14,OV
Pnar,24.82,92.26,Austroasiatic,17,0
Goemai,8.74,9.72,Afro-Asiatic,14,VO
English,53,-1,Indo-European,7,VO
Nung,-29.71,19.08,Tuu,18,VO
Bora,-2,-72.26,Boran,19,VSO
Nafsan,-17.7,168.38,Austronesian,11,OV
Komnzo,-8.65,141.52,Yam,20,OV
Kamas,55.07,94.83,Uralic,21,VO
Kakabe,10.6,-11.44,Mande,22,OV
Daakaka,-16.27,168.01,Austronesian,11,VO

Next in your R script, load the following packages. You might have to install them first:

library(ggplot2)
library(maps)
library(sf) #for advanced mapping options
library(viridisLite) #for pretty, color-blind friendly color palettes

Read in your map data and your language coordinates and save them to short variables like “map” and “df” (for data frame).

df <- read.csv("typology.txt")
head(df) #shows you the beginning of the data, good for trouble shooting
map <- map_data("world")

In order to plot your language coordinates with additional information about language families, do this:

#family
ggplot() +
  geom_polygon(data = map, aes(x = long, y = lat, group=group), fill="#dddddd") + #plots the world map in the background in light grey
  geom_point(data = df, aes(x = Longitude, y = Latitude, fill = factor(Family)), shape=21, size=3) + #plots the language coordinates
  theme_minimal()+ #fewer embellishments
  coord_sf()+ #nice proportions
  scale_fill_viridis_d(option = "plasma")+ #color scheme
  labs(fill = "Family", y="",x="") #labels
Plot of languages as they are geographically distributed across the world, with color indicating language family

To get information about word order patterns, instead use this:

#word order
ggplot() +
  geom_polygon(data = map, aes(x = long, y = lat, group = group), fill="#dddddd") +
  geom_point(data = df, aes(x = Longitude, y = Latitude, fill = factor(Word.order)), shape=21,  size=3) +
  theme_minimal()+
  coord_sf()+
  scale_fill_viridis_d(option = "plasma")+
  labs(fill = "Word Order", y="",x="")

And since newbies sometimes use my tutorials: If you don’t understand something here, don’t give up, google it! Everyone does it, and most questions you might have, have already been asked and answered by someone somewhere.