I’m working with a database called international_education
from the world_bank_intl_education
dataset of bigquery-public-data
.
FIELDS
country_name
country_code
indicator_name
indicator_code
value
year
My aim is to plot a line graph with countries who have had the biggest and smallest change in Population growth (annual %) (one of the indicator_name
values).
I have done this below using two partitions finding the first and last value of the year by each country but I’m rough on my SQL and am wondering if there is a way to optimize this formula.
query = """
WITH differences AS
(
SELECT country_name, year, value,
FIRST_VALUE(value)
OVER (
PARTITION BY country_name
ORDER BY year
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS small_value,
LAST_VALUE(value)
OVER (
PARTITION BY country_name
ORDER BY year
RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS large_value
FROM `bigquery-public-data.world_bank_intl_education.international_education`
WHERE indicator_name = 'Population growth (annual %)'
ORDER BY year
)
SELECT country_name, year, (large_value-small_value) AS total_range, value
FROM differences
ORDER BY total_range
"""
Convert to pandas dataframe.
df= wbed.query_to_pandas_safe(query)
df.head(10)
Resulting table.
country_name year total_range value
0 United Arab Emirates 1970 -13.195183 14.446942
1 United Arab Emirates 1971 -13.195183 16.881671
2 United Arab Emirates 1972 -13.195183 17.689814
3 United Arab Emirates 1973 -13.195183 17.695296
4 United Arab Emirates 1974 -13.195183 17.125615
5 United Arab Emirates 1975 -13.195183 16.211873
6 United Arab Emirates 1976 -13.195183 15.450884
7 United Arab Emirates 1977 -13.195183 14.530119
8 United Arab Emirates 1978 -13.195183 13.033461
9 United Arab Emirates 1979 -13.195183 11.071306
I would then plot this with python as follows.
all_countries = df.groupby('country_name', as_index=False).max().sort_values(by='total_range').country_name.values
countries = np.concatenate((all_countries[:3], all_countries[-4:]))
plt.figure(figsize=(16, 8))
sns.lineplot(x='year',y='value', data=df[df.country_name.isin(countries)], hue='country_name')
Advertisement
Answer
You don’t need the CTE and you don’t need the window frame definitions. So this should be equivalent:
SELECT country_name, year, value,
(first_value(value) OVER (PARTITION BY country_name ORDER BY YEAR DESC) -
first_value(value) OVER (PARTITION BY country_name ORDER BY YEAR)
) as total_range
FROM `bigquery-public-data.world_bank_intl_education.international_education`
WHERE indicator_name = 'Population growth (annual %)';
Note that LAST_VALUE()
is finicky with window frame definitions. So I routinely just use FIRST_VALUE()
with the order by reversed.
If you want just one row per country, then you need aggregation. BigQuery doesn’t have “first” and “last” aggregation functions, but they are very easy to do with arrays:
SELECT country_name,
((array_agg(value ORDER BY year DESC LIMIT 1))[ordinal(1)] -
(array_agg(value ORDER BY year LIMIT 1))[ordinal(1)]
) as total_range
FROM `bigquery-public-data.world_bank_intl_education.international_education`
WHERE indicator_name = 'Population growth (annual %)'
GROUP BY country_name
ORDER BY total_range;