**********
crosstab()
**********
Description
===========
Returns up to 3 DataFrames depending on what desired. Can calculate row, column,
or cell percentages if requested. Otherwise, counts are returned as the default.
DataFrame 1 is always the crosstabulation results, the other 2 DataFrames
returned depends on the options selected which is determined by the arguments
*test* and *expected_freqs*. If all 3 options are returned, then the order
of the returned DataFrames is as follows: crosstabulation results, :math:`\chi^2`
test results with effect size of Cramer's Phi or V depending on size of table, and
the expected frequency table.
Parameters
==========
Input
------
**crosstab(group1, group2, prop= None, test = False, margins= True,
correction = None, cramer_correction = None, exact = False, expected_freqs= False)**
* **group1** and **group2**, requires the data to be a Pandas Series
* **prop**, can either be 'row', 'col', or 'cell'. 'row' will calculate
the row percentages, 'column' will calculate the column percentages, and 'cell'
will calculate the cell percentage based on the entire sample
* **test**, can take "chi-square", "g-test", "mcnemar", or "fisher".
* If "chi-square", the chi-square (:math:`\chi^2`) test of independence :footcite:p:`scipy_chi2` will
be calculated and returned in a second DataFrame.
* If "g-test", will conduct the G-test (likelihood-ratio :math:`\chi^2`) :footcite:p:`scipy_chi2` and
the results will be returned in a second DataFrame.
* If "fisher", will conduct Fisher's exact test :footcite:p:`scipy_fisher`.
* If "mcnemar", will conduct the McNemar :math:`\chi^2` :footcite:p:`statsmodels_mcnemar` test for paired
nominal data.
* **margins**, if False will return a crosstabulation table without the total
counts for each group. This argument is only supported for counts; the margins
will always be returned for the percentages
* **correction**, if True, applies the Yates' correction for continuity. Valid
argument for *chi-square"*, *"g-test"*, and *"mcnemar"*.
* **cramer_correction**, if True, applies the bias correction developed by Tschuprow (1925) to Cramer's V.
* **exact**, is only a valid option for when the *"mcnemar"* test is selected. In that
case, *exact = True* will then the binomal distribution will be used. If false
(default), the :math:`\chi^2` distribution is used.
* **expected_freqs**, if True, will return a DataFrame that contains the
expected counts for each cell. Not a valid argurment for *mcnemar* test.
Returns
--------
Up to 3 Pandas DataFrames will be returned within a tuple:
* First DataFrame is always the crosstab table with either the counts,
cell, row, or column percentages
* Second DataFrame is either the test results or the expected frequencies.
If a test is selected and expected frequencies are desired, the second
DataFrame will be the test results; otherwise, if just expected frequencies
are desired, the second DataFrame will be that and there will not be a
third DataFrame returned.
* Third DataFrame is always the expected frequencies
.. note:: If conducting a McNemar test, make sure the outcomes in both variables
are labelled the same.
Effect Size Measures Formulas
=============================
.. note::
If adjusted :math:`\chi^2` values are used in the test's calculation, then those
adjusted :math:`\chi^2` values are also used to calculate effect size.
Cramer's Phi (2x2 table)
------------------------
For analyses were it's a 2x2 table, the following formula is used to
calculate Cramer's Phi (:math:`\phi`) :footcite:p:`cramer2016`:
.. math::
\phi = \sqrt{\frac{\chi^2}{N}}
Where N = total number of observations in the analysis
Cramer's V (RxC where R or C > 2)
---------------------------------
For analyses were it's a table that is larger than a 2x2, the
following formula is used to calculate Cramer's V :footcite:p:`cramer2016`:
.. math::
V = \sqrt{\frac{\chi^2}{(N*(k - 1))}}
Where K is the number of categories for either R or C (whichever has fewer
categories)
.. math::
\tilde{V} = \sqrt\frac{\tilde{\phi}^2}{\text{min}(\tilde{r} - 1, \tilde{c} - 1)}
Where r is the number of rows and c is the number of columns, and
.. math::
\tilde{\phi}^2 = \text{max}(0, \frac{\chi^2}{n} - \frac{(c - 1)(r - 1)}{n - 1}) \\
\tilde{c} = c - \frac{(c - 1)^2}{n - 1} \\
\tilde{r} = r - \frac{(r - 1)^2}{n - 1}
Examples
========
Loading Packages and Data
-------------------------
.. code:: python
import researchpy, pandas, numpy
numpy.random.seed(123)
df = pandas.DataFrame(numpy.random.randint(3, size= (101, 3)),
columns= ['disease', 'severity', 'alive'])
df.head()
.. raw:: html
|
disease |
severity |
alive |
| 0 |
2 |
1 |
2 |
| 1 |
2 |
0 |
2 |
| 2 |
2 |
1 |
2 |
| 3 |
1 |
2 |
1 |
| 4 |
0 |
1 |
2 |
Crosstabulation with Frequency
------------------------------
.. code:: python
# If only two Series are passed it will output a crosstabulation with margin totals.
# This is the same as pandas.crosstab(), except for researchpy.crosstab() returns
# a table with hierarchical indexing for better exporting format style.
researchpy.crosstab(df['disease'], df['alive'])
.. raw:: html
|
alive |
|
0 |
1 |
2 |
All |
| disease |
|
|
|
|
| 0 |
9 |
14 |
7 |
30 |
| 1 |
7 |
9 |
15 |
31 |
| 2 |
7 |
17 |
16 |
40 |
| All |
23 |
40 |
38 |
101 |
Crosstabulation with Cell Percentages
-------------------------------------
Cell percentages are calculated by taking the frequency of the cell and dividing it by the total N.
For example, the cell proportion for :math:`\text{disease}_0` and :math:`\text{alive}_0` = :math:`\frac{9}{101}`.
.. code:: python
crosstab = researchpy.crosstab(df['disease'], df['alive'], prop= "cell")
crosstab
.. raw:: html
|
alive |
|
0 |
1 |
2 |
All |
| disease |
|
|
|
|
| 0 |
8.91 |
13.86 |
6.93 |
29.70 |
| 1 |
6.93 |
8.91 |
14.85 |
30.69 |
| 2 |
6.93 |
16.83 |
15.84 |
39.60 |
| All |
22.77 |
39.60 |
37.62 |
100.00 |
Crosstabulation with Row Percentages
-------------------------------------
.. code:: python
crosstab = researchpy.crosstab(df['disease'], df['alive'], prop= "row")
crosstab
.. raw:: html
|
alive |
|
0 |
1 |
2 |
All |
| disease |
|
|
|
|
| 0 |
30.00 |
46.67 |
23.33 |
100.0 |
| 1 |
22.58 |
29.03 |
48.39 |
100.0 |
| 2 |
17.50 |
42.50 |
40.00 |
100.0 |
| All |
22.77 |
39.60 |
37.62 |
100.0 |
Crosstabulation with Column Percentages
---------------------------------------
.. code:: python
crosstab = researchpy.crosstab(df['disease'], df['alive'], prop= "col")
crosstab
.. raw:: html
|
alive |
|
0 |
1 |
2 |
All |
| disease |
|
|
|
|
| 0 |
39.13 |
35.0 |
18.42 |
29.70 |
| 1 |
30.43 |
22.5 |
39.47 |
30.69 |
| 2 |
30.43 |
42.5 |
42.11 |
39.60 |
| All |
100.00 |
100.0 |
100.00 |
100.00 |
Chi Squared (:math:`\chi^2`) Test of Independence
--------------------------------------------------
.. code:: python
# To conduct a Chi-square test of independence, pass "chi-square" in the "test =" argument.
# This will also output an effect size; either Cramer's Phi if it a 2x2 table, or
# Cramer's V is larger than 2x2.
# This will return 2 DataFrames as a tuple, 1 with the crosstabulation and the other with the
# test results. It's rather ugly, the recommended way to output is in the next example
researchpy.crosstab(df['disease'], df['alive'], test= "chi-square")
.. parsed-literal::
( alive
0 1 2 All
disease
0 9 14 7 30
1 7 9 15 31
2 7 17 16 40
All 23 40 38 101, Chi-square test results
0 Pearson Chi-square ( 4.0) = 5.1573
1 p-value = 0.2715
2 Cramer's V = 0.3196)
.. code:: python
# To clean up the output, assign each DataFrame to an object. This allows
# for a cleaner view and each DataFrame to be exported
crosstab, res = researchpy.crosstab(df['disease'], df['alive'], test= "chi-square")
crosstab
.. raw:: html
|
alive |
|
0 |
1 |
2 |
All |
| disease |
|
|
|
|
| 0 |
9 |
14 |
7 |
30 |
| 1 |
7 |
9 |
15 |
31 |
| 2 |
7 |
17 |
16 |
40 |
| All |
23 |
40 |
38 |
101 |
.. code:: python
res
.. raw:: html
|
Chi-square test |
results |
| 0 |
Pearson Chi-square ( 4.0) = |
5.1573 |
| 1 |
p-value = |
0.2715 |
| 2 |
Cramer's V = |
0.3196 |
.. code:: python
# To get the expected frequencies, pass "True" in "expected_freqs="
crosstab, res, expected = researchpy.crosstab(df['disease'], df['alive'], test= "chi-square", expected_freqs= True)
expected
.. raw:: html
|
alive |
|
0 |
1 |
2 |
| disease |
|
|
|
| 0 |
6.831683 |
11.881188 |
11.287129 |
| 1 |
7.059406 |
12.277228 |
11.663366 |
| 2 |
9.108911 |
15.841584 |
15.049505 |
G-test
--------
.. code:: python
crosstab, res = researchpy.crosstab(df['disease'], df['alive'], test= "g-test")
res
.. raw:: html
|
G-test |
results |
| 0 |
Log-likelihood ratio ( 4.0) = |
5.3808 |
| 1 |
p-value = |
0.2504 |
| 2 |
Cramer's V = |
0.3264 |
Fisher's Exact test
-------------------
.. code:: python
# Need 2x2 data for Fisher's test.
numpy.random.seed(345)
df = pandas.DataFrame(numpy.random.randint(2, size= (90, 2)),
columns= ['tx', 'cured'])
crosstab, res = researchpy.crosstab(df['tx'], df['cured'], test= "fisher")
crosstab
.. raw:: html
|
cured |
|
0 |
1 |
All |
| tx |
|
|
|
| 0 |
25 |
17 |
42 |
| 1 |
20 |
28 |
48 |
| All |
45 |
45 |
90 |
.. code:: python
res
.. raw:: html
|
Fisher's exact test |
results |
| 0 |
Odds ratio = |
2.0588 |
| 1 |
2 sided p-value = |
0.1387 |
| 2 |
Left tail p-value = |
0.9717 |
| 3 |
Right tail p-value = |
0.0694 |
| 4 |
Cramer's phi = |
0.1782 |
McNemar test
-------------
Make sure that the outcomes are labelled the same in both variables.
.. code:: python
numpy.random.seed(345)
df = pandas.DataFrame(numpy.random.randint(2, size= (90, 2)),
columns= ['time1', 'time2'])
crosstab, res = researchpy.crosstab(df['time1'], df['time2'], test= "mcnemar")
crosstab
.. raw:: html
|
time2 |
|
0 |
1 |
All |
| time1 |
|
|
|
| 0 |
25 |
17 |
42 |
| 1 |
20 |
28 |
48 |
| All |
45 |
45 |
90 |
.. code:: python
res
.. raw:: html
|
McNemar |
results |
| 0 |
McNemar's Chi-square ( 1.0) = |
0.2432 |
| 1 |
p-value = |
0.6219 |
| 2 |
Cramer's phi = |
0.0520 |
References
==========
.. footbibliography::