Grouping and Summarizing in SQL ——————————————————
The Group By clause in SQL organizes output into logical groups based on a variable. Once variables are grouped, we can then SELECT different things based on those groups, like the COUNT of a group, the MAX or MIN of a group, or the AVG of a group.
Let’s first consider some examples using qualitative data. For example, here is some data on the different customers and their respective regions.
SELECT CompanyName, RegionFROM CustomersLIMIT10
Displaying records 1 - 10
CompanyName
Region
Alfreds Futterkiste
Western Europe
Ana Trujillo Emparedados y helados
Central America
Antonio Moreno Taquería
Central America
Around the Horn
British Isles
Berglunds snabbköp
Northern Europe
Blauer See Delikatessen
Western Europe
Blondesddsl père et fils
Western Europe
Bólido Comidas preparadas
Southern Europe
Bon app’
Western Europe
Bottom-Dollar Markets
North America
It is just a long list, not super useful. We already know that we could count the number of customers in each region with geom_bar().
Sometimes you don’t want a graph though, you need a number. This is why you need to know to group and count. You could try to read these off the graph I guess, but only knowing how to produce one geom_bar() is pretty limiting. Let’s look at how to create the numbers that were used to generate the graph.
COUNT and GROUP BY
The COUNT function and GROUP BY clause are SQL’s primary tools for summarizing data. GROUP BY, instructs the database to slice that giant dataset into distinct buckets based on shared values in a specific column.
Once the data is sorted into these buckets, COUNT steps in to calculate the total number of rows within each individual group. For example, if you group an orders table by customer_id, the database aggregates the rows for each unique customer and uses COUNT to output exactly how many purchases each individual made.
Here is the same query, but now grouped to produce the same numbers that determine the size of the bars in the previous geom_bar() graph.
SELECTCOUNT(*), RegionFROM CustomersGROUPBY Region
Displaying records 1 - 10
COUNT(*)
Region
2
NA
8
British Isles
5
Central America
1
Eastern Europe
16
North America
4
Northern Europe
3
Scandinavia
16
South America
10
Southern Europe
28
Western Europe
Notice how the only changes I made to the code involved adding a GROUP BY (always after any WHERE statements) and instead of only SELECTing the CompanyName’s I now select the COUNT(*). This represents the number of rows in the data that correspond to each group.
Tip
COUNTING and GROUPING makes some weird variable names. Graphing something like COUNT() can be hard, so I highly recommend adding a quick alias (eg. COUNT(*) AS “n” or something similar). I like to use “n” to represent the number of rows in each group because it is used frequently in statistics for this purpose.
The HAVING clause
Grouped data can be filtered using the HAVING clause. It works like a WHERE clause, but for groups when you want to filter based on an attribute possessed by THE GROUP. Importantly, a WHERE filter will not override a HAVING. The order of the commands is also not interchangeable. WHERE comes first, then GROUP BY, then HAVING.
SELECTCOUNT(*) AS n, RegionFROM CustomersGROUPBY RegionHAVING n >10
Look at the difference between the output of these two queries. Can you see how HAVING has altered the results?
SELECTCOUNT(*) AS n, RegionFROM CustomersGROUPBY Region
ggplot(data = myquery1,aes(y = Region, x = n)) +geom_col(fill ="lightblue") +labs(title ="Plot showing all customers in all regions")
SELECTCOUNT(*) AS n, RegionFROM CustomersGROUPBY RegionHAVING n >10
ggplot(data = myquery2,aes(y = Region, x = n)) +geom_col(fill ="lightblue") +labs(title ="Plot with data filtered using HAVING n > 10")
New geoms
geom_col()
When we count and group ourselves, we can no longer rely on geom_bar() to produce our counts. We must explicitly tell GGPLOT which column contains our variable names, and which column contains our counts. We then have access to some new geoms. It is like a bar chart, but for data that has already been summarized. I use geom_col() in the two examples above. Also, notice that I graph the name of my alias (n), not COUNT(*).
You can also use two groupings together with fill inside a geom_col to create stacked bar charts like we saw in Week 2. Here I select both customer cities and regions and group by BOTH of these, before creating a stacked geom_col(). Now we can see which cities are best represented in each region.
SELECTCOUNT(*) AS n, Region, CityFROM CustomersGROUPBY Region, City
ggplot(data = myquery3,aes(y = Region, x = n, fill = City)) +geom_col()
That’s too many cities to be useful. Let’s filter it using HAVING.
SELECTCOUNT(*) AS n, Region, CityFROM CustomersGROUPBY Region, CityHAVING n >2
ggplot(data = myquery3,aes(y = Region, x = n, fill = City)) +geom_col() +labs(title ="Graph showing regions and cities HAVING n > 2")
Warning
Note that if you have multi-group pairings in your code, each multi-group must meet the HAVING criteria. In this case, HAVING n > 10 would filter out all of the data, as there are no cities that have more than 10 customers. For this reason, I used HAVING n > 2. The graph shows all region/city pairings that have more than two customers
Lollipop Charts (Optional)
Tip
These next two geoms are entirely optional, but if you have an idea you’d like to try to get working, just reach out to me!
Here is a sleek looking alternative to a geom_col(). It uses two separate geoms, a line segment and a geom_point to create a similar looking aesthetic. You only need to change the variable names for the x, y , xend and yend to get this to work for your own data.
ggplot(data = myquery5, aes(x = n, y = Region)) +geom_segment(aes(x =0, xend = n, y = Region, yend = Region), color ="gray70", linewidth =1) +geom_point(color ="#2c3e50", size =4) +theme_minimal(base_size =12)
Heatmaps (Optional)
Heatmaps allow you to visualize the concentration of various 2x2 combinations of variables. They offer another way to visualize information from a stacked bar chart. The example below shows how many Customers are located in each Country in Europe. Darker squares indicate higher customer concentrations.
ggplot(myquery6, aes(y = Country, x = Region, fill = n)) +geom_tile(color ="white", linewidth =0.5) +scale_fill_gradient(low ="#e0f2fe", high ="#0369a1") +theme_minimal(base_size =12)
HAVING and GROUP BY with Quantitative Data
Counting is great for things that aren’t quantitative numbers. If you have quantitative variables, you can use different functions including SUM(), AVG, MAX etc. Here, I again look at the Product reorder level variable we considered in Week 2, grouped by Category. The data is filtered to just include the Produce and Beverages categories.
SELECT CategoryName, Reorderlevel,SUM(CAST(reorderlevel asINTEGER)) as total_reorder, MAX(CAST(reorderlevel asINTEGER)) as biggest_reorder, MIN(CAST(reorderlevel asINTEGER)) as smallest_reorder, AVG(CAST(reorderlevel asINTEGER)) as avg_reorderFROM ProductsINNERJOIN CategoriesON products.CategoryID = Categories.CategoryIDWHERE CategoryName IN ("Produce","Beverages")GROUPBY CategoryName
2 records
CategoryName
ReorderLevel
total_reorder
biggest_reorder
smallest_reorder
avg_reorder
Beverages
0
195
30
0
16.25
Produce
0
25
10
0
5.00
Compare the table above to boxplots of the same data. Can you find the min, max and average values on the boxplots?