
3. Joining Datasets¶
Unlike standard SQL, SODA does not (currently) support the joining of separate datasets. As you recall, this is a fundamental part of working with data. Often, there are different sets of data for the same entity, and it doesn’t make sense to store them all in one dataset. Even if you wanted to, this would quickly increase the # of columns in a dataset.
For example, the School Locations dataset we have already looked at stores both the geographical information of schools in Calgary. The School Enrolment dataset stores the enrollment information for schools. It should be quite obvious that these two datasets should remain separate. Most often, you would not need enrollment #s when looking for locations, and vice versa. If the datasets were permanently combined, you likely have a lot of redundant columns. Moreover, the school enrollment dataset is keyed on the school year. You would have to repeat the geographical data in each row, which would be a poor use of space.
But, what if we wanted to demonstrate additional features of the scattermapbox by visualizing school enrollment? We will need to obtain data from the two seperate datasets, and dynamically join them together. As mentioned, we can’t do it in SODA, but we can do so programatically using the pandas ‘merge’ function.
import requests as rq
import pandas as pd
import io as io
domain = "https://data.calgary.ca/resource/"
uuid_school_locations = "fd9t-tdn2"
uuid_school_enrollment = "9qye-mibh"
def run_query(domain, uuid, query):
session = rq.Session()
results = session.get(domain + uuid +".csv?$query=" + query)
dataframe = pd.read_csv(io.StringIO(results.content.decode('utf-8')))
return dataframe
The first dataset contains latitude and longitude
query = """
SELECT
*
"""
calgary_school_location = run_query(domain, uuid_school_locations, query)
calgary_school_location
| name | board | type | name_ab | address_ab | city | province | grades | postsecond | data_source | ... | ecs | elem | junior_h | senior_h | process_dt | postal_cod | structure_id | longitude | latitude | location | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Mount Royal University - Lincoln Park Campus | NaN | Post Secondary | NaN | NaN | Calgary | AB | NaN | Y | Post Secondary Data | ... | N | N | N | N | 2020-05-31T00:00:00.000Z | T3E6K6 | 1537443 | -114.132720 | 51.012805 | \n, \n(51.01280496081857, -114.13272034345307) |
| 1 | SAIT - Art Smith Aero Centre | NaN | Post Secondary | NaN | NaN | Calgary | AB | NaN | Y | Post Secondary Data | ... | N | N | N | N | 2020-05-31T00:00:00.000Z | T2E9B5 | 1374654 | -114.005456 | 51.101159 | \n, \n(51.10115940461863, -114.00545627006355) |
| 2 | North Middle School Campus | Foundations for the Future Charter Academy Cha... | Charter | North Middle School Campus | 211 McKnight Blvd. NE | Calgary | AB | Elementary/Junior High | N | Provincial Data | ... | N | Y | Y | N | 2020-05-31T00:00:00.000Z | T2E5S7 | 1384092 | -114.060601 | 51.095453 | \n, \n(51.095453050755296, -114.06060095936921) |
| 3 | David Thompson | The Calgary School Division | Public School | David Thompson School | 9320 Arbour Crescent S.E. | Calgary | AB | Elementary/Junior High | N | Provincial Data | ... | N | Y | Y | N | 2020-05-31T00:00:00.000Z | T2J1K4 | 1382277 | -114.054651 | 50.969404 | \n, \n(50.96940358851814, -114.05465136499893) |
| 4 | Louise Dean | The Calgary School Division | Public School | Louise Dean School | 120 - 23 Street N.W. | Calgary | AB | Senior High | N | Provincial Data | ... | N | N | N | Y | 2020-05-31T00:00:00.000Z | T2N2P1 | 1382214 | -114.114682 | 51.053151 | \n, \n(51.05315071429952, -114.11468247256505) |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 464 | Calgary Girls Charter | Calgary Girls' School Society | Charter | Calgary Girls Charter School | 6304 Larkspur Way S.W. | Calgary | AB | Elementary/Junior High | N | Provincial Data | ... | N | Y | Y | N | 2020-05-31T00:00:00.000Z | T3E5P7 | 1382200 | -114.114754 | 50.999077 | \n, \n(50.99907702433947, -114.1147539295334) |
| 465 | Bishop O'Byrne High | The Calgary Roman Catholic Separate School Div... | Separate School | Bishop O'Byrne High School | Suite 500 333 Shawville Boulevard S.E. | Calgary | AB | Senior High | N | Provincial Data | ... | N | N | N | Y | 2020-05-31T00:00:00.000Z | T2Y4H3 | 1559224 | -114.064567 | 50.898092 | \n, \n(50.89809184871791, -114.06456676845627) |
| 466 | Sundance | The Calgary School Division | Public School | Sundance School | 200 Sunmills Drive S.E. | Calgary | AB | Elementary | N | Provincial Data | ... | N | Y | N | N | 2020-05-31T00:00:00.000Z | T2X2N9 | 1382957 | -114.046813 | 50.900661 | \n, \n(50.90066056931126, -114.04681306876033) |
| 467 | Sir Winston Churchill High | The Calgary School Division | Public School | Sir Winston Churchill High School | 5220 Northland Drive N.W. | Calgary | AB | Senior High | N | Provincial Data | ... | N | N | N | Y | 2020-05-31T00:00:00.000Z | T2L2J6 | 1383224 | -114.139990 | 51.100113 | \n, \n(51.10011269545306, -114.1399900255147) |
| 468 | St. Damien | The Calgary Roman Catholic Separate School Div... | Separate School | St. Damien School | 3619 - 28 Street S.E. | Calgary | AB | Elementary | N | Provincial Data | ... | N | Y | N | N | 2020-05-31T00:00:00.000Z | T2B2J1 | 1382602 | -113.992197 | 51.021910 | \n, \n(51.0219097042796, -113.99219707864222) |
469 rows × 21 columns
The second dataset contains enrollment data. Note that the enrollment dataset also contains a school_year column, which makes sense, as enrollment values do change. However, our intent is to visualize enrollment at a specific point in time, so we use the WHERE clause to filter out rows that do not match that time.
query = """
SELECT
*
WHERE
school_year = '2019-2020'
"""
calgary_school_enrollment = run_query(domain, uuid_school_enrollment, query)
print(calgary_school_enrollment)
school_year school_authority_category \
0 2019-2020 Public
1 2019-2020 Public
2 2019-2020 Public
3 2019-2020 Separate
4 2019-2020 Separate
.. ... ...
385 2019-2020 Separate
386 2019-2020 Public
387 2019-2020 Separate
388 2019-2020 Public
389 2019-2020 Private
school_authority_name school_authority_code \
0 The Calgary School Division 3030
1 The Calgary School Division 3030
2 The Calgary School Division 3030
3 The Calgary Roman Catholic Separate School Div... 4010
4 The Calgary Roman Catholic Separate School Div... 4010
.. ... ...
385 The Calgary Roman Catholic Separate School Div... 4010
386 The Calgary School Division 3030
387 The Calgary Roman Catholic Separate School Div... 4010
388 The Calgary School Division 3030
389 Calgary Waldorf School Society 9224
school_name school_code ecs grade_1 grade_2 grade_3 \
0 Haysboro School 9316 21.0 24.0 25.0 26.0
1 Guy Weadick School 9343 33.0 40.0 44.0 62.0
2 Silver Springs School 9256 28.0 38.0 33.0 36.0
3 Holy Angels School 389 31.0 25.0 37.0 28.0
4 St. Helena School 8711 NaN NaN NaN NaN
.. ... ... ... ... ... ...
385 St. Anthony 8515 NaN NaN 2.0 2.0
386 Elboya School 9627 26.0 45.0 43.0 50.0
387 St. Patrick School 8507 54.0 31.0 40.0 34.0
388 Dr. E. W. Coffin School 9214 21.0 27.0 26.0 36.0
389 Calgary Waldorf School 9949 17.0 19.0 28.0 22.0
grade_4 grade_5 grade_6 grade_7 grade_8 grade_9 grade_10 grade_11 \
0 25.0 31.0 23.0 NaN NaN NaN NaN NaN
1 48.0 52.0 36.0 NaN NaN NaN NaN NaN
2 41.0 43.0 44.0 NaN NaN NaN NaN NaN
3 33.0 35.0 24.0 NaN NaN NaN NaN NaN
4 NaN NaN NaN 116.0 85.0 116.0 NaN NaN
.. ... ... ... ... ... ... ... ...
385 3.0 2.0 NaN 3.0 1.0 2.0 4.0 1.0
386 36.0 96.0 115.0 110.0 101.0 87.0 NaN NaN
387 29.0 30.0 31.0 NaN NaN NaN NaN NaN
388 26.0 29.0 18.0 NaN NaN NaN NaN NaN
389 22.0 21.0 21.0 22.0 20.0 19.0 NaN NaN
grade_12 total
0 NaN 175
1 NaN 315
2 NaN 263
3 NaN 213
4 NaN 317
.. ... ...
385 7.0 27
386 NaN 709
387 NaN 249
388 NaN 183
389 NaN 211
[390 rows x 20 columns]
Data from CSV files¶
We could instead use pandas to import data from CSV (comma separated values) files.
import pandas as pd
calgary_school_location = pd.read_csv("https://data.calgary.ca/resource/64t2-ax4d.csv")
calgary_school_enrollment = pd.read_csv("https://data.calgary.ca/resource/9qye-mibh.csv?$where=School_Year%20=%20'2019-2020'")
calgary_school_location
| NAME | BOARD | TYPE | NAME_AB | ADDRESS_AB | CITY | PROVINCE | GRADES | POSTSECOND | DATA_SOURCE | ... | POSTAL_COD | STRUCTURE_ID | longitude | latitude | location | Ward Boundaries | City Quadrants | Calgary Communities | Ward Boundaries 2013-2017 | CALGIS CENSUS COMMUNITY DIST 2015 Shape File | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | Mount Royal University - Lincoln Park Campus | NaN | Post Secondary | NaN | NaN | Calgary | AB | NaN | Y | Post Secondary Data | ... | T3E6K6 | 1537443 | -114.132720 | 51.012805 | (51.01280496081857, -114.13272034345307) | 8 | 1 | 42 | 2 | 11 |
| 1 | SAIT - Art Smith Aero Centre | NaN | Post Secondary | NaN | NaN | Calgary | AB | NaN | Y | Post Secondary Data | ... | T2E9B5 | 1374654 | -114.005456 | 51.101159 | (51.10115940461863, -114.00545627006355) | 11 | 4 | 86 | 1 | 226 |
| 2 | North Middle School Campus | Foundations for the Future Charter Academy Cha... | Charter | North Middle School Campus | 211 McKnight Blvd. NE | Calgary | AB | Elementary/Junior High | N | Provincial Data | ... | T2E5S7 | 1384092 | -114.060601 | 51.095453 | (51.095453050755296, -114.06060095936921) | 2 | 4 | 241 | 4 | 101 |
| 3 | David Thompson | The Calgary School Division | Public School | David Thompson School | 9320 Arbour Crescent S.E. | Calgary | AB | Elementary/Junior High | N | Provincial Data | ... | T2J1K4 | 1382277 | -114.054651 | 50.969404 | (50.96940358851814, -114.05465136499893) | 10 | 3 | 174 | 5 | 81 |
| 4 | Louise Dean | The Calgary School Division | Public School | Louise Dean School | 120 - 23 Street N.W. | Calgary | AB | Senior High | N | Provincial Data | ... | T2N2P1 | 1382214 | -114.114682 | 51.053151 | (51.05315071429952, -114.11468247256505) | 7 | 2 | 280 | 7 | 35 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 464 | Calgary Girls Charter | Calgary Girls' School Society | Charter | Calgary Girls Charter School | 6304 Larkspur Way S.W. | Calgary | AB | Elementary/Junior High | N | Provincial Data | ... | T3E5P7 | 1382200 | -114.114754 | 50.999077 | (50.99907702433947, -114.1147539295334) | 8 | 1 | 122 | 5 | 265 |
| 465 | Bishop O'Byrne High | The Calgary Roman Catholic Separate School Div... | Separate School | Bishop O'Byrne High School | Suite 500 333 Shawville Boulevard S.E. | Calgary | AB | Senior High | N | Provincial Data | ... | T2Y4H3 | 1559224 | -114.064567 | 50.898092 | (50.89809184871791, -114.06456676845627) | 5 | 3 | 115 | 6 | 281 |
| 466 | Sundance | The Calgary School Division | Public School | Sundance School | 200 Sunmills Drive S.E. | Calgary | AB | Elementary | N | Provincial Data | ... | T2X2N9 | 1382957 | -114.046813 | 50.900661 | (50.90066056931126, -114.04681306876033) | 6 | 3 | 52 | 13 | 128 |
| 467 | Sir Winston Churchill High | The Calgary School Division | Public School | Sir Winston Churchill High School | 5220 Northland Drive N.W. | Calgary | AB | Senior High | N | Provincial Data | ... | T2L2J6 | 1383224 | -114.139990 | 51.100113 | (51.10011269545306, -114.1399900255147) | 7 | 2 | 50 | 4 | 280 |
| 468 | St. Damien | The Calgary Roman Catholic Separate School Div... | Separate School | St. Damien School | 3619 - 28 Street S.E. | Calgary | AB | Elementary | N | Provincial Data | ... | T2B2J1 | 1382602 | -113.992197 | 51.021910 | (51.0219097042796, -113.99219707864222) | 10 | 3 | 196 | 11 | 1 |
469 rows × 26 columns
Joining data sets¶
Whichever method we use to import the data, we can now join the two datasets on the school name (school_name in calgary_school_location and name or NAME in calgary_school_enrollment) as the values in these fields match up. In proper SQL parlance, they share a key. Again, the dataset has already been filtered for a single school year. This means that there should be no duplicates of school name in the school enrollment dataset.
Thus, we can do a simple left join with the enrollment on the left and the locations on the right. That way, our resulting dataframe will only have data on schools that have enrollment data. We would run into problems later on if there are NaN (aka null) values in the total field.
calgary_school_location_enrollment = pd.merge(left=calgary_school_enrollment,
right=calgary_school_location,
how='left',
left_on='school_name',
right_on='NAME' )
# right_on='name' ) #use this line if you imported data using Requests
calgary_school_location_enrollment
| school_year | school_authority_category | school_authority_name | school_authority_code | school_name | school_code | ecs | grade_1 | grade_2 | grade_3 | ... | POSTAL_COD | STRUCTURE_ID | longitude | latitude | location | Ward Boundaries | City Quadrants | Calgary Communities | Ward Boundaries 2013-2017 | CALGIS CENSUS COMMUNITY DIST 2015 Shape File | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2019-2020 | Public | The Calgary School Division | 3030 | Haysboro School | 9316 | 21.0 | 24.0 | 25.0 | 26.0 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 1 | 2019-2020 | Public | The Calgary School Division | 3030 | Guy Weadick School | 9343 | 33.0 | 40.0 | 44.0 | 62.0 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 2 | 2019-2020 | Public | The Calgary School Division | 3030 | Silver Springs School | 9256 | 28.0 | 38.0 | 33.0 | 36.0 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 3 | 2019-2020 | Separate | The Calgary Roman Catholic Separate School Div... | 4010 | Holy Angels School | 389 | 31.0 | 25.0 | 37.0 | 28.0 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 4 | 2019-2020 | Separate | The Calgary Roman Catholic Separate School Div... | 4010 | St. Helena School | 8711 | NaN | NaN | NaN | NaN | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 386 | 2019-2020 | Separate | The Calgary Roman Catholic Separate School Div... | 4010 | St. Anthony | 8515 | NaN | NaN | 2.0 | 2.0 | ... | T2S2N5 | 1377870.0 | -114.079295 | 51.010108 | (51.01010774903789, -114.0792951385963) | 8.0 | 1.0 | 143.0 | 5.0 | 275.0 |
| 387 | 2019-2020 | Public | The Calgary School Division | 3030 | Elboya School | 9627 | 26.0 | 45.0 | 43.0 | 50.0 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 388 | 2019-2020 | Separate | The Calgary Roman Catholic Separate School Div... | 4010 | St. Patrick School | 8507 | 54.0 | 31.0 | 40.0 | 34.0 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 389 | 2019-2020 | Public | The Calgary School Division | 3030 | Dr. E. W. Coffin School | 9214 | 21.0 | 27.0 | 26.0 | 36.0 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
| 390 | 2019-2020 | Private | Calgary Waldorf School Society | 9224 | Calgary Waldorf School | 9949 | 17.0 | 19.0 | 28.0 | 22.0 | ... | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
391 rows × 46 columns
Visualizing the data¶
We will now visualize the resulting dataset, and use the size and color to add two extra dimensions of data - the total enrollment and the school authority.
Note¶
the
showlegendparameter is used to hide the legend, but this is only to clean up the presentation!
import plotly.express as px
figure1 = px.scatter_mapbox(calgary_school_location_enrollment,
size="total",
color="school_authority_name",
#showlegend= False,
color_continuous_scale=px.colors.cyclical.IceFire,
size_max=45,
lat="latitude",
lon="longitude",
hover_name="school_name",
hover_data=["TYPE", "GRADES", "ADDRESS_AB"],
#hover_data=["type", "grades", "address_ab"], #use this line if you imported data using Requests
zoom=10,
height=600)
figure1.update_layout(mapbox_style="open-street-map")
figure1.update_layout(showlegend= False)
figure1.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
Conclusion¶
This notebook introduced the ability to join datasets. The last notebook in this series is filtering datasets.
