import pandas as pd
url = "https://raw.githubusercontent.com/rfordatascience/tidytuesday/main/data/2020/2020-01-21/spotify_songs.csv"
df = pd.read_csv(url)2 Tabular data and summary statistics with pandas
2.1 What is pandas?
The standard Python library for working with tabular data
- Read data from CSV, Excel, SQL…
- Store data in DataFrames
- Select, filter, transform, and summarise data
- Perform fast operations
- Create simple plots
2.2 DataFrames
A DataFrame is like an Excel sheet.
- Rows = observations
- Columns = variables
- Each column has a name and a type
In this class, we’ll only consider tabular data. So no images, audio, or video.
Reading a file. Use pd.read_csv() to load a DataFrame from a CSV file.
There are many options to customise the reading process, but the defaults are usually good enough, see docs
df.head()| track_id | track_name | track_artist | track_popularity | track_album_id | track_album_name | track_album_release_date | playlist_name | playlist_id | playlist_genre | ... | key | loudness | mode | speechiness | acousticness | instrumentalness | liveness | valence | tempo | duration_ms | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 6f807x0ima9a1j3VPbc7VN | I Don't Care (with Justin Bieber) - Loud Luxur... | Ed Sheeran | 66 | 2oCs0DGTsRO98Gh5ZSl2Cx | I Don't Care (with Justin Bieber) [Loud Luxury... | 2019-06-14 | Pop Remix | 37i9dQZF1DXcZDD7cfEKhW | pop | ... | 6 | -2.634 | 1 | 0.0583 | 0.1020 | 0.000000 | 0.0653 | 0.518 | 122.036 | 194754 |
| 1 | 0r7CVbZTWZgbTCYdfa2P31 | Memories - Dillon Francis Remix | Maroon 5 | 67 | 63rPSO264uRjW1X5E6cWv6 | Memories (Dillon Francis Remix) | 2019-12-13 | Pop Remix | 37i9dQZF1DXcZDD7cfEKhW | pop | ... | 11 | -4.969 | 1 | 0.0373 | 0.0724 | 0.004210 | 0.3570 | 0.693 | 99.972 | 162600 |
| 2 | 1z1Hg7Vb0AhHDiEmnDE79l | All the Time - Don Diablo Remix | Zara Larsson | 70 | 1HoSmj2eLcsrR0vE9gThr4 | All the Time (Don Diablo Remix) | 2019-07-05 | Pop Remix | 37i9dQZF1DXcZDD7cfEKhW | pop | ... | 1 | -3.432 | 0 | 0.0742 | 0.0794 | 0.000023 | 0.1100 | 0.613 | 124.008 | 176616 |
| 3 | 75FpbthrwQmzHlBJLuGdC7 | Call You Mine - Keanu Silva Remix | The Chainsmokers | 60 | 1nqYsOef1yKKuGOVchbsk6 | Call You Mine - The Remixes | 2019-07-19 | Pop Remix | 37i9dQZF1DXcZDD7cfEKhW | pop | ... | 7 | -3.778 | 1 | 0.1020 | 0.0287 | 0.000009 | 0.2040 | 0.277 | 121.956 | 169093 |
| 4 | 1e8PAfcKUYoKkxPhrHqw4x | Someone You Loved - Future Humans Remix | Lewis Capaldi | 69 | 7m7vv9wlQ4i0LFuJiE2zsQ | Someone You Loved (Future Humans Remix) | 2019-03-05 | Pop Remix | 37i9dQZF1DXcZDD7cfEKhW | pop | ... | 1 | -4.672 | 1 | 0.0359 | 0.0803 | 0.000000 | 0.0833 | 0.725 | 123.976 | 189052 |
5 rows × 23 columns
Viewing the data. Use .head()/.tail() to see the top/bottom of your DataFrame
df.tail()| track_id | track_name | track_artist | track_popularity | track_album_id | track_album_name | track_album_release_date | playlist_name | playlist_id | playlist_genre | ... | key | loudness | mode | speechiness | acousticness | instrumentalness | liveness | valence | tempo | duration_ms | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 32828 | 7bxnKAamR3snQ1VGLuVfC1 | City Of Lights - Official Radio Edit | Lush & Simon | 42 | 2azRoBBWEEEYhqV6sb7JrT | City Of Lights (Vocal Mix) | 2014-04-28 | ♥ EDM LOVE 2020 | 6jI1gFr6ANFtT8MmTvA2Ux | edm | ... | 2 | -1.814 | 1 | 0.0936 | 0.076600 | 0.000000 | 0.0668 | 0.2100 | 128.170 | 204375 |
| 32829 | 5Aevni09Em4575077nkWHz | Closer - Sultan & Ned Shepard Remix | Tegan and Sara | 20 | 6kD6KLxj7s8eCE3ABvAyf5 | Closer Remixed | 2013-03-08 | ♥ EDM LOVE 2020 | 6jI1gFr6ANFtT8MmTvA2Ux | edm | ... | 0 | -4.462 | 1 | 0.0420 | 0.001710 | 0.004270 | 0.3750 | 0.4000 | 128.041 | 353120 |
| 32830 | 7ImMqPP3Q1yfUHvsdn7wEo | Sweet Surrender - Radio Edit | Starkillers | 14 | 0ltWNSY9JgxoIZO4VzuCa6 | Sweet Surrender (Radio Edit) | 2014-04-21 | ♥ EDM LOVE 2020 | 6jI1gFr6ANFtT8MmTvA2Ux | edm | ... | 6 | -4.899 | 0 | 0.0481 | 0.108000 | 0.000001 | 0.1500 | 0.4360 | 127.989 | 210112 |
| 32831 | 2m69mhnfQ1Oq6lGtXuYhgX | Only For You - Maor Levi Remix | Mat Zo | 15 | 1fGrOkHnHJcStl14zNx8Jy | Only For You (Remixes) | 2014-01-01 | ♥ EDM LOVE 2020 | 6jI1gFr6ANFtT8MmTvA2Ux | edm | ... | 2 | -3.361 | 1 | 0.1090 | 0.007920 | 0.127000 | 0.3430 | 0.3080 | 128.008 | 367432 |
| 32832 | 29zWqhca3zt5NsckZqDf6c | Typhoon - Original Mix | Julian Calor | 27 | 0X3mUOm6MhxR7PzxG95rAo | Typhoon/Storm | 2014-03-03 | ♥ EDM LOVE 2020 | 6jI1gFr6ANFtT8MmTvA2Ux | edm | ... | 5 | -4.571 | 0 | 0.0385 | 0.000133 | 0.341000 | 0.7420 | 0.0894 | 127.984 | 337500 |
5 rows × 23 columns
You can also look at the shape of the DataFrame and the column names.
print(df.columns)
print(df.shape)
print(df.dtypes)Index(['track_id', 'track_name', 'track_artist', 'track_popularity',
'track_album_id', 'track_album_name', 'track_album_release_date',
'playlist_name', 'playlist_id', 'playlist_genre', 'playlist_subgenre',
'danceability', 'energy', 'key', 'loudness', 'mode', 'speechiness',
'acousticness', 'instrumentalness', 'liveness', 'valence', 'tempo',
'duration_ms'],
dtype='str')
(32833, 23)
track_id str
track_name str
track_artist str
track_popularity int64
track_album_id str
track_album_name str
track_album_release_date str
playlist_name str
playlist_id str
playlist_genre str
playlist_subgenre str
danceability float64
energy float64
key int64
loudness float64
mode int64
speechiness float64
acousticness float64
instrumentalness float64
liveness float64
valence float64
tempo float64
duration_ms int64
dtype: object
To select a single column, use the column name in brackets:
df["track_name"]0 I Don't Care (with Justin Bieber) - Loud Luxur...
1 Memories - Dillon Francis Remix
2 All the Time - Don Diablo Remix
3 Call You Mine - Keanu Silva Remix
4 Someone You Loved - Future Humans Remix
...
32828 City Of Lights - Official Radio Edit
32829 Closer - Sultan & Ned Shepard Remix
32830 Sweet Surrender - Radio Edit
32831 Only For You - Maor Levi Remix
32832 Typhoon - Original Mix
Name: track_name, Length: 32833, dtype: str
This returns a Series, which is like a single-column DataFrame. If the column name has spaces or special characters, you can also use the dot notation:
df.track_name0 I Don't Care (with Justin Bieber) - Loud Luxur...
1 Memories - Dillon Francis Remix
2 All the Time - Don Diablo Remix
3 Call You Mine - Keanu Silva Remix
4 Someone You Loved - Future Humans Remix
...
32828 City Of Lights - Official Radio Edit
32829 Closer - Sultan & Ned Shepard Remix
32830 Sweet Surrender - Radio Edit
32831 Only For You - Maor Levi Remix
32832 Typhoon - Original Mix
Name: track_name, Length: 32833, dtype: str
To select multiple columns:
df[["track_name", "key", "tempo"]]| track_name | key | tempo | |
|---|---|---|---|
| 0 | I Don't Care (with Justin Bieber) - Loud Luxur... | 6 | 122.036 |
| 1 | Memories - Dillon Francis Remix | 11 | 99.972 |
| 2 | All the Time - Don Diablo Remix | 1 | 124.008 |
| 3 | Call You Mine - Keanu Silva Remix | 7 | 121.956 |
| 4 | Someone You Loved - Future Humans Remix | 1 | 123.976 |
| ... | ... | ... | ... |
| 32828 | City Of Lights - Official Radio Edit | 2 | 128.170 |
| 32829 | Closer - Sultan & Ned Shepard Remix | 0 | 128.041 |
| 32830 | Sweet Surrender - Radio Edit | 6 | 127.989 |
| 32831 | Only For You - Maor Levi Remix | 2 | 128.008 |
| 32832 | Typhoon - Original Mix | 5 | 127.984 |
32833 rows × 3 columns
Variable types. In tabular data, a variable is either categorical or numerical. Different statistical methods apply to each type.
If a variable has a small number of possible values, we call it categorical: with/without kids, smoker/non-smoker, country of residence, etc. Also called qualitative or nominal variables (because they name categories).
Roughly, a variable is numerical if it can take many possible values, all of which are numbers. Also called quantitative or continuous variables.
A couple of remarks:
- A good rule of thumb is that if you can compute an average, the variable is quantitative. If you want to count how many individuals fall into each category, the variable is categorical.
- Some variables are numbers but can be treated as categorical. For example, a variable recording the number of children in a family can be categorical if we group families by that number (families with 0 children, families with 1 child, families with 2 children, etc.). Being categorical or quantitative depends on what we want to do with the variable, not on the values themselves.
An identifier variable is one where there are as many values as there are individuals. In general, identifiers are not useful for analysis. Even if they are numbers, we don’t compute averages of student IDs, and we don’t count how many students have each ID.
Ordinal variables are not numbers but have a natural order: e.g. “low”, “medium”, “high” or “freshman”, “sophomore”, “junior”, “senior”. Can be treated as categorical, but we can also compute averages of ordinal variables by assigning numbers to the categories (e.g. 1, 2, 3, 4 for school years). It all depends on the context.
Assigning the correct type to each column helps you understand your data
Example. Convert a column with .astype(), e.g. key (currently a number) really is a category:
df["key"] = df["key"].astype("category")
df.dtypestrack_id str
track_name str
track_artist str
track_popularity int64
track_album_id str
track_album_name str
track_album_release_date str
playlist_name str
playlist_id str
playlist_genre str
playlist_subgenre str
danceability float64
energy float64
key category
loudness float64
mode int64
speechiness float64
acousticness float64
instrumentalness float64
liveness float64
valence float64
tempo float64
duration_ms int64
dtype: object
Let pandas guess the easy types for every column at once with .convert_dtypes():
df.convert_dtypes().dtypestrack_id string
track_name string
track_artist string
track_popularity Int64
track_album_id string
track_album_name string
track_album_release_date string
playlist_name string
playlist_id string
playlist_genre string
playlist_subgenre string
danceability Float64
energy Float64
key category
loudness Float64
mode Int64
speechiness Float64
acousticness Float64
instrumentalness Float64
liveness Float64
valence Float64
tempo Float64
duration_ms Int64
dtype: object
You can get the top k largest or smallest values of a column with .nlargest() and .nsmallest():
df.nlargest(5, "danceability")[["track_name", "track_artist", "danceability"]]| track_name | track_artist | danceability | |
|---|---|---|---|
| 31627 | If Only I Could (feat. Steve Lucas) - Liem Remix | Fusion Groove Orchestra | 0.983 |
| 27330 | Mega Run - Downtown Jump | DJ ZsuZsu | 0.981 |
| 806 | Ice Ice Baby | Vanilla Ice | 0.979 |
| 19253 | Enseñame a Soñar - Original Mix | DJ Goozo | 0.979 |
| 21220 | Cha Cha Slide - Original Live Platinum Band Mix | DJ Casper | 0.978 |
df.nsmallest(5, "tempo")[["track_name", "track_artist", "tempo"]]| track_name | track_artist | tempo | |
|---|---|---|---|
| 11363 | Hi, How're You Doin'? | DREAMS COME TRUE | 0.000 |
| 1204 | Knowing | Davina Leone | 35.477 |
| 13764 | Still Crazy After All These Years | Paul Simon | 37.114 |
| 5629 | autumn daze | charlie toØ human | 38.985 |
| 25793 | The Christmas Song | Nat King Cole | 46.169 |
2.3 Statistics
Calculate the mean value for each column:
df.mean(numeric_only=True)track_popularity 42.477081
danceability 0.654850
energy 0.698619
loudness -6.719499
mode 0.565711
speechiness 0.107068
acousticness 0.175334
instrumentalness 0.084747
liveness 0.190176
valence 0.510561
tempo 120.881132
duration_ms 225799.811622
dtype: float64
Works also for other statistics, e.g. the standard deviation:
df.std(numeric_only=True)track_popularity 24.984074
danceability 0.145085
energy 0.180910
loudness 2.988436
mode 0.495671
speechiness 0.101314
acousticness 0.219633
instrumentalness 0.224230
liveness 0.154317
valence 0.233146
tempo 26.903624
duration_ms 59834.006182
dtype: float64
The function describe() gives a summary of statistics for each column:
df.describe()| track_popularity | danceability | energy | loudness | mode | speechiness | acousticness | instrumentalness | liveness | valence | tempo | duration_ms | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| count | 32833.000000 | 32833.000000 | 32833.000000 | 32833.000000 | 32833.000000 | 32833.000000 | 32833.000000 | 32833.000000 | 32833.000000 | 32833.000000 | 32833.000000 | 32833.000000 |
| mean | 42.477081 | 0.654850 | 0.698619 | -6.719499 | 0.565711 | 0.107068 | 0.175334 | 0.084747 | 0.190176 | 0.510561 | 120.881132 | 225799.811622 |
| std | 24.984074 | 0.145085 | 0.180910 | 2.988436 | 0.495671 | 0.101314 | 0.219633 | 0.224230 | 0.154317 | 0.233146 | 26.903624 | 59834.006182 |
| min | 0.000000 | 0.000000 | 0.000175 | -46.448000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 0.000000 | 4000.000000 |
| 25% | 24.000000 | 0.563000 | 0.581000 | -8.171000 | 0.000000 | 0.041000 | 0.015100 | 0.000000 | 0.092700 | 0.331000 | 99.960000 | 187819.000000 |
| 50% | 45.000000 | 0.672000 | 0.721000 | -6.166000 | 1.000000 | 0.062500 | 0.080400 | 0.000016 | 0.127000 | 0.512000 | 121.984000 | 216000.000000 |
| 75% | 62.000000 | 0.761000 | 0.840000 | -4.645000 | 1.000000 | 0.132000 | 0.255000 | 0.004830 | 0.248000 | 0.693000 | 133.918000 | 253585.000000 |
| max | 100.000000 | 0.983000 | 1.000000 | 1.275000 | 1.000000 | 0.918000 | 0.994000 | 0.994000 | 0.996000 | 0.991000 | 239.440000 | 517810.000000 |
Finally, for categorical variables, we can count how many individuals fall into each category with value_counts():
df["playlist_genre"].value_counts()playlist_genre
edm 6043
rap 5746
pop 5507
r&b 5431
latin 5155
rock 4951
Name: count, dtype: int64
Or simply the number of unique values in a column with nunique():
df["playlist_genre"].nunique()6
2.4 Learning resources
Data Science - NumPy, Matplotlib, and Pandas in Python (video)