-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path04-basic_statistics.Rmd
More file actions
299 lines (202 loc) · 20 KB
/
Copy path04-basic_statistics.Rmd
File metadata and controls
299 lines (202 loc) · 20 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
---
output:
html_document:
toc: yes
html_notebook: default
pdf_document:
toc: yes
---
# Summarizing data
## Summary statistics
```{r echo=FALSE, eval=TRUE, message=FALSE, warning=FALSE}
library(knitr)
options(scipen = 999)
#This code automatically tidies code so that it does not reach over the page
opts_chunk$set(tidy.opts=list(width.cutoff=50),tidy=TRUE, rownames.print = FALSE, rows.print = 10)
opts_chunk$set(cache=T)
```
This section discusses how to produce and analyze basic summary statistics. Summary statistics are often used to describe variables in terms of 1) the central tendency of the frequency distribution, and 2) the dispersion of values.
<br>
A **measure of central tendency** is a single value that attempts to describe the data by identifying the central position within the data. There are various measures of central tendency as the following table shows.
Statistic | Description | Definition
---- | ------------------------------ | -----
Mean | The average value when you sum up all elements and divide by the number of elements | $\bar{X}=\frac{\sum_{i=1}^{n}{X_i}}{n}$
Mode | The value that occurs most frequently (i.e., the highest peak of the frequency distribution) |
Median | The middle value when the data are arranged in ascending or descending order (i.e., the 50th percentile) |
<br>
The **dispersion** refers to the degree to which the data is distributed around the central tendency and can be described in terms of the range, interquartile range, variance, and standard deviation.
Statistic | Description | Definition
---- | ------------------------------ | -----
Range | The difference between the largest and smallest values in the sample | $Range=X_{largest}-X_{smallest}$
Interquartile range | The range of the middle 50% of scores | $IQR=Q_3-Q_1$
Variance | The mean squared deviation of all the values of the mean | $s^2=\frac{1}{n-1}*\sum_{i=1}^{n}{(X_i-\bar{X})^2}$
Standard deviation | The square root of the variance | $s_x=\sqrt{s^2}$
<br>
The answer to the question which measures to use depends on the level of measurement. Based on the discussion in chapter 1, we make a distinction between categorical and continuous variables, for which different statistics are permissible as summarized in the following table.
OK to compute... | Nominal | Ordinal | Interval | Ratio
------------- | ------------- | ------------- | --- | ---
frequency distribution | Yes | Yes | Yes | Yes
median and percentiles | No | Yes | Yes | Yes
mean, standard deviation, standard error of the mean | No | No | Yes | Yes
ratio, or coefficient of variation | No | No | No | Yes
<br>
As an example data set, we will be using a data set containing music streaming data from a popular streaming service. Let's load and inspect the data first.
```{r echo=FALSE, eval=TRUE, message=FALSE, warning=FALSE}
set.seed(1)
sales_data <- read.csv2("https://raw.githubusercontent.com/WU-RDS/RMA/refs/heads/main/data/data_visualization.csv", sep = ";")
head(sales_data)
dim(sales_data)
```
The data set contains information about several purchases made in a number of stores in 2023. The `dim()`-function returns the dimensions of the data frame (i.e., the number of rows and columns). As can be seen, the data set comprises information for 5000 transactions and 7 variables (columns). The variables in the data set are:
* Date: date of purchase
* Store: Store ID/number
* Category: product category of the purchased item
* Brand: name of the brand of the purchased item
* Sales_Amount: the monetary value of the purchase (i.e., store's revenue)
* Units_Sold: number of units purchased
* Customer_Age: the age of the customer who made the purchase
In a first step, we need to make sure all variables are in the correct format, according to these variable definitions:
```{r, message=FALSE, warning=FALSE, eval=TRUE}
library(tidyverse)
sales_data <- sales_data %>% # pipe data into mutate
dplyr::mutate(Date = as.Date(Date), # convert to date
Store = as.factor(Store), # convert to factor w. new labels
Category = as.factor(Category), # convert to factor with values as labels
Brand = as.factor(Brand))
head(sales_data)
```
In the following sections, we will inspect the data in more detail.
### Categorical variables
Categorical variables contain a finite number of categories or distinct groups and are also known as qualitative or non-metric variables. There are different types of categorical variables:
* **Nominal variables**: variables that have two or more categories but no logical order (e.g., store ID, brand name). A dichotomous variable (also referred to as dummy variable or binary variable) is simply a nominal variable that only has two categories (absent in this data set).
* **Ordinal variables**: variables that have two or more categories that can also be ordered or ranked (absent in this data set).
Let's now start to investigate the **nominal variables** in our data set.
As the table above shows, the only permissible operation with nominal variables is counting. That is, we can inspect the frequency distribution, which tells us how many observations we have per category. The ```table()``` function creates a frequency table that counts how many observations we have in each category.
```{r, message=FALSE, warning=FALSE, eval=TRUE}
table(sales_data$Category) #absolute frequencies
table(sales_data$Store) #absolute frequencies
```
The numbers associated with the factor level in the output tell you, how many observations there are per category. For example, there are `r nrow(sales_data[sales_data$Category == "Clothing", ])` purchases from the Clothing category; also, 532 are from Store No. 9.
Often, we are interested in the relative frequencies (share of observations), which can be obtained by using the ```prop.table()``` function.
```{r, message=FALSE, warning=FALSE, eval=TRUE}
prop.table(table(sales_data$Category)) #relative frequencies
prop.table(table(sales_data$Store)) #relative frequencies
```
```{r include=FALSE}
grocery_share <- prop.table(table(sales_data$Category)) # first create the relative frequencies table
grocery_share <- round(100*grocery_share[names(grocery_share) == "Grocery"], digits = 1) # extract the value that satisfies the condition (where the name of category is "Grocery")
grocery_share
store_2_share <- prop.table(table(sales_data$Store))
store_2_share <- round(100*store_2_share[names(store_2_share) == "Store 2"], digits = 1)
store_2_share
```
Now the output gives you the relative frequencies. For example, the share of purchases (transactions) of groceries is ~`r grocery_share`%, ~`r store_2_share`% of purchases are from Store number 2.
Note that the above output shows the overall relative frequencies. In many cases, it is meaningful to consider conditional relative frequencies. This can be achieved by adding a ```,1``` to the ```prop.table()``` command, which tells R to compute the relative frequencies by row (which is in our case the Store variable). The following code can be used to show the relative frequency of purchases from specific categories by store
```{r, message=FALSE, warning=FALSE, eval=TRUE}
prop.table(table(select(sales_data, Store, Category)), 1) #conditional relative frequencies
```
As can be seen, the the share of transactions from the Clothing category is high in all stores, with the highest share of ~30% of transactions in Store 8.
### Continuous variables
#### Descriptive statistics
Continuous variables (also know as metric variables) are numeric variables that can take on any value on a measurement scale (i.e., there is an infinite number of values between any two values). There are different types of continuous variables as we have seen in chapter 1:
* **Interval variables**: while the zero point is arbitrary, equal intervals on the scale represent equal differences in the property being measured. E.g., on a temperature scale measured in Celsius the difference between a temperature of 15 degrees and 25 degrees is the same difference as between 25 degrees and 35 degrees but the zero point is arbitrary (there are different scales to measure temperature, such as Fahrenheit or Celsius, and zero in this case doesn't indicate the absence of temperature).
* **Ratio variables**: has all the properties of an interval variable, but also has an absolute zero point. When the variable equals 0.0, it means that there is none of that variable (e.g., sales amount and customer age in our example).
For interval and ratio variables we can compute the mean as a measure of central tendency, as well as the variance and the standard deviation as measures of dispersion. Computing descriptive statistics for continuous variables is easy and there are many functions. The easiest are:
```{r, message=FALSE, warning=FALSE, eval=TRUE}
# average customer age
avg_age <- mean(sales_data$Customer_Age)
avg_age
# median customer age
median_age <- median(sales_data$Customer_Age)
median_age
# or using quantile()
median_age <- quantile(sales_data$Customer_Age, 0.5, type = 1)
median_age
# customer age by percentiles
quantile(sales_data$Customer_Age, c(0.25,0.5,0.75), type = 1)
# mode customer age
library(DescTools)
mode_age <- Mode(sales_data$Customer_Age)
mode_age
```
We can also use functions that return multiple statistics at once; those are available in different packages that let you calculate summary statistics (including the ```summary()``` function from the ```base``` package). In this tutorial, we will use the ```describe()``` function from the ```psych``` package. Note that you could just as well use other packages to compute the descriptive statistics (e.g., the ```stat.desc()``` function from the ```pastecs``` package). Which one you choose depends on what type of information you seek (the results provide slightly different information) and on personal preferences.
We could, for example, compute the summary statistics for the variables "sales amount", "units sold", and "customer age" in our data set as follows:
```{r message=FALSE, warning=FALSE, paged.print = FALSE}
library(psych)
psych::describe(select(sales_data, Sales_Amount, Units_Sold, Customer_Age))
```
You can see that the output contains measures of central tendency (e.g., the mean) and dispersion (e.g., standard deviation) for the selected variables. It can be seen, for example, that the mean of the sales amount variable is 279.29 while the median is 202.95. This already tells us something about the distribution of the data. Because the mean is higher (though not as substantially as it could get, in theory) than the median, we can conclude that there are a some purchases that costed somewhat more than others, potentially resulting in a right skew of the distribution. The median as a measure of central tendency is generally less susceptible to outliers.
In the above command, we used the ```psych::``` prefix to avoid confusion and to make sure that R uses the ```describe()``` function from the ```psych``` package since there are many other packages that also contain a ```desribe()``` function. Note that you could also compute these statistics separately by using the respective functions (e.g., ```mean()```, ```sd()```, ```median()```, ```min()```, ```max()```, etc.). There are many options for additional statistics for this function. For example, you could add the argument `IQR = TRUE` to add the interquartile range to the output.
The ```psych``` package also contains the ```describeBy()``` function, which lets you compute the summary statistics by sub-groups separately. For example, we could compute the summary statistics by category as follows:
```{r message=FALSE, warning=FALSE}
describeBy(select(sales_data, Sales_Amount, Units_Sold, Customer_Age), sales_data$Category, skew = FALSE, range = FALSE)
```
In this example, we used the arguments `skew = FALSE` and `range = FALSE` to exclude some statistics from the output.
R is open to user contributions and various users have contributed packages that aim at making it easier for researchers to summarize statistics. For example, the <a href="https://cran.r-project.org/web/packages/summarytools/vignettes/Recommendations-rmarkdown.html" target="_blank">summarytools</a> package can be used to summarize the variables. If you would like to use this package and you are a Mac user, you may need to also install XQuartz (X11) too. To do this, go to <a href="https://www.xquartz.org/" target="_blank">this page</a> and download the XQuartz-2.7.7.dmg, then open the downloaded folder and click XQuartz.pkg and follow the instruction on screen and install XQuartz. If you still encouter an error after installing XQuartz, you may find a solution <a href="href="https://www.xquartz.org/" target="_blank">here</a>.
```{r, echo=FALSE, message=FALSE, warning=FALSE, results='asis'}
library(summarytools)
st_css()
```
```{r, message=FALSE, error = FALSE, warning = FALSE, results='asis'}
library(summarytools)
print(dfSummary(select(sales_data, Sales_Amount, Units_Sold, Customer_Age, Store, Category, Brand), plain.ascii = FALSE, style = "grid",valid.col = FALSE, tmp.img.dir = "tmp", graph.magnif = .65), method = 'render',headings = FALSE,footnote= NA)
```
The 'Missing' column in the output above gives us information about missing values. It this case, there are no missing values; however, in reality there are usually at least a couple of lost or not recorded values. To get more precise analysis results, we might want to exclude these observations by creating a "complete" subset of our data. Imagine that we have a missing value in the variable "Sales_Amount"; we would create a subset by filtering that hypothetical observation out:
```{r message=FALSE, warning=FALSE}
sales_data_full <- filter(sales_data, !is.na(Sales_Amount))
```
In the command above, `!is.na()` is used to filter the rows for observations where the respective variable does not have missing values. The "!" in this case translates to "is not" and the function `is.na()` checks for missing values. Hence, the entire statement can be read as "select the rows from the 'sales_data' data set where the values of the 'Sales_Amount' variable are not missing".
As you can see, the output also includes a visualization of the frequency distribution using a histogram for the continuous variables and a bar chart for the categorical variables. The frequency distribution is an important element that allows us to assign probabilities to observed values if the observations come from a known probability distribution. How to derive these probability statements will be discussed next.
#### Using frequency distributions to go beyond the data
The frequency distribution can be used to make statements about the probability that a certain observed value will occur if the observations come from a known probability distribution. For normally distributed data, the following table can be used to look up the probability that a certain value will occur. For example, the value of -1.96 has a probability of 2.5% (i.e., .0250).
```{r,echo=FALSE,out.width = '70%',fig.align='center',fig.cap = "Standard normal table"}
knitr::include_graphics("./images/prob_table.JPG")
```
There are two things worth noting. First, the normal distribution has two tails as the following figure shows and we need to take the probability mass at each side of the distribution into account. Hence, there is a 2.5% probability of observing a value of -1.96 or smaller and a 2.5% of observing a value of 1.96 or larger. Hence, the probability mass within this interval is 0.95.
```{r,echo=FALSE,out.width = '70%',fig.align='center',fig.cap = "Standard normal distribution"}
knitr::include_graphics("./images/normal_distribution.JPG")
```
The second point is related to the scale of the distribution. Since the variables that we will collect can be measured at many different scales (e.g., number of streams, duration in milliseconds), we need a way to convert the scale into a standardized measure that would allow us to compare the observations against the values from the probability table. The **standardized variate**, or z-score, allows us to do exactly that. It is computed as follows:
$$\begin{align}
Z=\frac{X_i-\bar{X}}{s}
\end{align}
$$
By subtracting the mean of the variable from each observation and dividing by the standard deviation, the data is converted to a scale with mean = 0 and SD = 1, so we can use the tables of probabilities for the normal distribution to see how likely it is that a particular score will occur in the data. In other words, **the z-score tells us how many standard deviations above or below the mean a particular x-value is**.
To see how this works in practice, let's inspect the distribution of the 'customer age' variable from the sales data set. The `hist()`-function can be used to draw the corresponding histogram.
```{r message=FALSE, warning=FALSE,fig.align='center',fig.cap = "Histogram of tempo variable"}
hist(sales_data$Customer_Age)
```
In this case, the variable is measured on the scale "years lived" (so it takes values from 0 to, potentially, infinity). To standardize this variable, we will subtract the mean of this variable from each observation and then divide by the standard deviation. We can compute the standardized variable by hand as follows:
```{r message=FALSE, warning=FALSE}
sales_data$age_std <- (sales_data$Customer_Age - mean(sales_data$Customer_Age))/sd(sales_data$Customer_Age)
#sales_data$age_std <- scale(sales_data$Customer_Age)
```
If we create the histogram again, we can see that the scale has changed and now we can compare the standardized values to the values we find in the probability table.
```{r message=FALSE, warning=FALSE,fig.align='center',fig.cap = "Histogram of standardized tempo variable"}
hist(sales_data$age_std)
```
Instead of manually comparing the observed values to the values in the table, it is much easier to use the in-built functions to obtain the probabilities. The `pnorm()`-function gives the probability of obtaining values lower than the indicated values (i.e., the probability mass left of that value). For the value of 1.96, this probability mass is ~0.025, in line with the table above.
```{r message=FALSE, warning=FALSE}
pnorm(-1.96)
```
To also take the other end of the distribution into consideration, we would need to multiply this value by to. This way, we arrive at a value of 5%.
```{r message=FALSE, warning=FALSE}
pnorm(-1.96)*2
```
Regarding the standard normal distribution, it is helpful to remember the following numbers, indicating the points on the standard normal distribution, where the sum of the probability mass to the left at the lower end and to the right of the upper end exceed a certain threshold:
* +/-**1.645** - 10% of probability mass outside this region
* +/-**1.960** - 5% of probability mass outside this region
* +/-**2.580** - 1% of probability mass outside this region
Going back to our example, we could also ask: what is the probability of obtaining the minimum (or maximum) observed value in our data? The minimum value on the standardized scale is:
```{r message=FALSE, warning=FALSE}
min(sales_data$age_std)
```
And the associated probability is:
```{r message=FALSE, warning=FALSE}
pnorm(min(sales_data$age_std))*2
```
Although the probability of observing this minimum value is low, there are very few observations in the extreme regions at each end of the histogram, so this doesn't seem too unusual. As a rule of thumb, you can remember that 68% of the observations of a normally distributed variable should be within 1 standard deviation of the mean, 95% within 2 standard deviations, and 99.7% within 3 standard deviations. This is also shown in the following plot:
```{r,echo=FALSE,out.width = '70%',fig.align='center',fig.cap = "The 68, 95, 99.7 rule (source: Wikipedia)"}
knitr::include_graphics("./images/prob_rule.JPG")
```
In case of our 'customer age' variable, we do observe values that are more than 3 standard deviations away from the mean. In this and other instances, checking the standardized values of a variable may help you to identify outliers. For example, if you conducted a survey and you would like to exclude respondents who answered the survey too fast, you may exclude cases with a low probability based on the distribution of the duration variable.