How to Make a Table
First, let's get some data. MASS package contains data about 93 cars on sale in the USA in 1993. They're stored in Cars93 object and include 27 features for each car, some of which are categorical. So let's load the MASS package and look at the type of vehicles included in cars93:
library(MASS)
Cars93$Type
We have 6 types of cars there. table function tells how many of each type we have:
prop.table converts it into fractions:
prop.table(table(Cars93$Type))
The same with the origin of cars:
table(Cars93$Origin)
prop.table(table(Cars93$Origin))
How to Make a Contingency Table
Great, we saw that our dataset contains a similar number of US and non-US cars and that the most prevalent types are Midsize and Small. However, maybe the US and non-US differ in type?
Let's look at types of cars with respect to their origin. We can use table again, but with two arguments now. First will become row variable and second will become column variable:
table(Cars93$Type, Cars93$Origin)
Now, we saw what everybody knows: Americans love large vehicles! The table above shows the joint distribution of two categorical variables (Type and Origin). Such tables are called contingency tables.
rowSums and colSums functions are really self-explanatory
(tab1<-table(Cars93$Type, Cars93$Origin))
rowSums(tab1)
colSums(tab1)
prop.table nested with table gives frequencies
prop.table(table(Cars93$Type, Cars93$Origin))
Conversion to percents is just multiplication by 100:
prop.table(table(Cars93$Type, Cars93$Origin))*100
Notice, that this is a joint probability distribution, from which we can see that e.g., about 7.5% of cars are small and of American origin.
More often, we are interested in the distribution of one variable within groups created by another. Here, distribution of car types among the US and (separately) non-US cars seems interesting. To get this, we use the margin argument to prop.table function. It tells where in rows (margin=1) or in columns (margin=2) grouping variable is:
prop.table(table(Cars93$Type, Cars93$Origin), margin=2)*100
Now we can easily see that small cars are twice as frequent in non-USA than in USA part of our dataset.
Also notice that percents add up to 100 in columns, while in joint distribution table (the one without margin argument), 100 was a sum of a whole table.
(tab2<-prop.table(table(Cars93$Type, Cars93$Origin), margin=2)*100)
colSums(tab2)
Chi-squared Test
The most common question that arises form contingency tables is if the row and column variables are independent. The most basic way to answer it is to run a chi-squared test. It is covered in great detail in this tutorial. Let's check if Type and Origin are independent:
chisq.test(Cars93$Type, Cars93$Origin)
Apparently, they're not, but we also got the Chi-squared approximation may be incorrect warning. This is because chi-squared statistic follows chi-squared distribution only approximately. The more observations we have, the better approximation is. chisq.test function throws the above warning whenever one of the expected counts is lower than 5 (for what an "expected count" is, see tutorial linked above).
Fisher's Exact Test
Fisher's exact test is an alternative to chi-squared test used mainly when a chi-squared approximation is not satisfactory. Let's run it:
fisher.test(Cars93$Type, Cars93$Origin)
Results are quite similar to those from chi-squared, but this is not always the case. One major drawback of Fisher's exact test is that for large tables (or large samples) it becomes computationally ineffective.
G-test
Another alternative is the so-called G-test. Its statistic is also approximately chi-squared distributed, but for small samples, this approximation is closer than one that chi-squared test uses. For G-test we can use GTest function from DescTools package. Results are again quite similar to two previous tests: Type and Origin are not independent.
library(DescTools)
GTest(Cars93$Type, Cars93$Origin)
Yates' Correction
In 2x2 contingency tables, the chi-squared test may be enhanced with Yates' continuity correction. It simply subtracts 0.5 from each | Observed - Expected | term in a chi-squared statistic. Again refer to this tutorial if you feel lost now. What is more, R applies Yates' correction automatically whenever it's necessary. Let's look at the availability of manual transmission versions of US and non-US cars:
(tab3<-table(Cars93$Man.trans.avail, Cars93$Origin))
chisq.test(tab3)
Yates' correction was applied automatically, and R told us about it.
3 (or more) Dimensional Table
Let's look at Man.trans.avail, Origin, and Type at once. table splits dataset according to variable provided as third:
table(Cars93$Man.trans.avail, Cars93$Origin, Cars93$Type)
ftable gives more compact view:
ftable(Cars93$Man.trans.avail, Cars93$Origin, Cars93$Type)
Cochran-Mantel-Haenszel Test
From table results above, it should be clear that relationship between Origin and Man.trans.avail differs with Type. For small and sporty cars, for example, there is no association: every US, as well as every non-US small or sporty car, comes in a manual version. On the other hand, most midsize US cars don't have a manual version, while most non-US cars of this type do. To account for possibly different relationships in strata, Cochran-Mantel-Haenszel test can be used:
mantelhaen.test(Cars93$Man.trans.avail, Cars93$Origin, Cars93$Type)
The third argument passed to mantelhaen.test identifies strata. Compare the above results to those without stratification (Yates' correction example above). Association is still present, but evidence for it is much weaker.
Association measures
Once we discovered some associations between variables, it's time to measure their strength. There are a plethora of various types of possible measures. Many of them are described here. Now I'll focus on two most widely used.
Cramer's V
V is based on the chi-squared statistic:
$$ V = \sqrt{\frac{\chi^2/N}{\min(C-1, R-1)}}, $$ where:
- N is a grand total of the contingency table (sum of all its cells),
- C is the number of columns
- R is the number of rows.
V ∈ [0; 1]. The larger V is, the stronger the relationship is between variables. V = 0 can be interpreted as independence (since V = 0 if and only if χ2 = 0). The main drawback of V is lack of precise interpretation. Is V = 0.6 strong, moderate or weak association?
CramerV function from DescTools can calculate it for us:
CramerV(Cars93$Type, Cars93$Origin)
Once again: is this strong, moderate or a weak association?
Goodman and Kruskal lambda
Goodman and Kruskal lambda is an example of measure based on the proportional reduction in variation. These kinds of measures aim to mimic R2 - coefficient of determination from linear regression:
- they take values from [0, 1]
- they express a fraction of variation explained by the independent variable.
Let's use a column-variable as the independent. Goodman and Kruskal's lambda formula is then: $$\lambda = \frac{L - \sum_j L_j}{L},$$ where:
- Lj is a sum of non-modal frequencies in j-th column,
- L is a sum of non-modal frequencies in "total" column
This is best explained with an example. Let's look at Type and Origin, the later being independent:
table(Cars93$Type, Cars93$Origin)
Mode of US column is 11, so L1 = 7 + 10 + 7 + 8 + 5 = 37. Mode of non-US column is 14, so L2 = 9 + 0 + 12 + 6 + 4 = 31. "Total" column is
rowSums(table(Cars93$Type, Cars93$Origin))
Mode is 22, so L = 16 + 11 + 21 + 14 + 9 = 71 and $$\lambda = \frac{71 - (37+31)}{71}=0.042$$
We can use Lambda function from DescTools package instead:
Lambda(Cars93$Type, Cars93$Origin, direction='row')
direction parameter determines where (in row or in column) the dependent variable is.
Origin explains only about 4% of the variation in Type. Notice from the formula that lambda defines variation as a dichotomy between belonging and not belonging to the largest group.
It's worth mentioning that lambda is zero whenever the modal category of each column is the same. Take a look at this table:
(lambdaTab<-cbind(c(0,100), c(49,51)))
chisq.test(lambdaTab)
Modal category is the same for each column (it's the second row). So lambda must be zero despite significant and visible association:
Lambda(lambdaTab, direction='row')
If you would like to learn more about statistics in R, take DataCamp's Statistical Modeling in R (Part 1) course.
How to Make a Table
First, let's get some data.
MASSpackage contains data about 93 cars on sale in the USA in 1993. They're stored inCars93object and include 27 features for each car, some of which are categorical. So let's load theMASSpackage and look at the type of vehicles included incars93:We have 6 types of cars there.
tablefunction tells how many of each type we have:prop.tableconverts it into fractions:The same with the origin of cars:
How to Make a Contingency Table
Great, we saw that our dataset contains a similar number of US and non-US cars and that the most prevalent types are Midsize and Small. However, maybe the US and non-US differ in type?
Let's look at types of cars with respect to their origin. We can use
tableagain, but with two arguments now. First will become row variable and second will become column variable:Now, we saw what everybody knows: Americans love large vehicles! The table above shows the joint distribution of two categorical variables (
TypeandOrigin). Such tables are called contingency tables.How to Get Marginals Form Contingency Table
rowSumsandcolSumsfunctions are really self-explanatoryHow to Get Percents Form Contingency Table
prop.tablenested withtablegives frequenciesConversion to percents is just multiplication by 100:
Notice, that this is a joint probability distribution, from which we can see that e.g., about 7.5% of cars are small and of American origin.
More often, we are interested in the distribution of one variable within groups created by another. Here, distribution of car types among the US and (separately) non-US cars seems interesting. To get this, we use the
marginargument toprop.tablefunction. It tells where in rows (margin=1) or in columns (margin=2) grouping variable is:Now we can easily see that small cars are twice as frequent in non-USA than in USA part of our dataset.
Also notice that percents add up to 100 in columns, while in joint distribution table (the one without
marginargument), 100 was a sum of a whole table.Chi-squared Test
The most common question that arises form contingency tables is if the row and column variables are independent. The most basic way to answer it is to run a chi-squared test. It is covered in great detail in this tutorial. Let's check if
TypeandOriginare independent:Apparently, they're not, but we also got the
Chi-squared approximation may be incorrectwarning. This is because chi-squared statistic follows chi-squared distribution only approximately. The more observations we have, the better approximation is.chisq.testfunction throws the above warning whenever one of the expected counts is lower than 5 (for what an "expected count" is, see tutorial linked above).Fisher's Exact Test
Fisher's exact test is an alternative to chi-squared test used mainly when a chi-squared approximation is not satisfactory. Let's run it:
Results are quite similar to those from chi-squared, but this is not always the case. One major drawback of Fisher's exact test is that for large tables (or large samples) it becomes computationally ineffective.
G-test
Another alternative is the so-called G-test. Its statistic is also approximately chi-squared distributed, but for small samples, this approximation is closer than one that chi-squared test uses. For G-test we can use
GTestfunction fromDescToolspackage. Results are again quite similar to two previous tests:TypeandOriginare not independent.Yates' Correction
In 2x2 contingency tables, the chi-squared test may be enhanced with Yates' continuity correction. It simply subtracts 0.5 from each | Observed - Expected | term in a chi-squared statistic. Again refer to this tutorial if you feel lost now. What is more,
Rapplies Yates' correction automatically whenever it's necessary. Let's look at the availability of manual transmission versions of US and non-US cars:Yates' correction was applied automatically, and
Rtold us about it.3 (or more) Dimensional Table
Let's look at
Man.trans.avail,Origin, andTypeat once.tablesplits dataset according to variable provided as third:ftablegives more compact view:Cochran-Mantel-Haenszel Test
From
tableresults above, it should be clear that relationship betweenOriginandMan.trans.availdiffers withType. For small and sporty cars, for example, there is no association: every US, as well as every non-US small or sporty car, comes in a manual version. On the other hand, most midsize US cars don't have a manual version, while most non-US cars of this type do. To account for possibly different relationships in strata, Cochran-Mantel-Haenszel test can be used:The third argument passed to
mantelhaen.testidentifies strata. Compare the above results to those without stratification (Yates' correction example above). Association is still present, but evidence for it is much weaker.Association measures
Once we discovered some associations between variables, it's time to measure their strength. There are a plethora of various types of possible measures. Many of them are described here. Now I'll focus on two most widely used.
Cramer's V
V is based on the chi-squared statistic:
$$ V = \sqrt{\frac{\chi^2/N}{\min(C-1, R-1)}}, $$ where:
V ∈ [0; 1]. The larger V is, the stronger the relationship is between variables. V = 0 can be interpreted as independence (since V = 0 if and only if χ2 = 0). The main drawback of V is lack of precise interpretation. Is V = 0.6 strong, moderate or weak association?
CramerVfunction fromDescToolscan calculate it for us:Once again: is this strong, moderate or a weak association?
Goodman and Kruskal lambda
Goodman and Kruskal lambda is an example of measure based on the proportional reduction in variation. These kinds of measures aim to mimic R2 - coefficient of determination from linear regression:
Let's use a column-variable as the independent. Goodman and Kruskal's lambda formula is then: $$\lambda = \frac{L - \sum_j L_j}{L},$$ where:
This is best explained with an example. Let's look at
TypeandOrigin, the later being independent:Mode of US column is 11, so L1 = 7 + 10 + 7 + 8 + 5 = 37. Mode of non-US column is 14, so L2 = 9 + 0 + 12 + 6 + 4 = 31. "Total" column is
Mode is 22, so L = 16 + 11 + 21 + 14 + 9 = 71 and $$\lambda = \frac{71 - (37+31)}{71}=0.042$$
We can use
Lambdafunction fromDescToolspackage instead:directionparameter determines where (inrowor incolumn) the dependent variable is.Originexplains only about 4% of the variation inType. Notice from the formula that lambda defines variation as a dichotomy between belonging and not belonging to the largest group.It's worth mentioning that lambda is zero whenever the modal category of each column is the same. Take a look at this table:
Modal category is the same for each column (it's the second row). So lambda must be zero despite significant and visible association:
If you would like to learn more about statistics in R, take DataCamp's Statistical Modeling in R (Part 1) course.