Loading...

Predicting Popularity on Spotify

This example uses the publicly-available Spotify from Kaggle to predict the popularity of Polish songs and artists on Spotify. We’ll also use a model to group artists together based on how similar their songs are.

You can download the Jupyter notebook of this study here.

Note

We are only using polish artists and a subset of the tracks dataset filtered by a handful of artists.

The tracks dataset (tracks.csv) have the following features:

Id represents the Id of the track generated by Spotify

Numerical:

  • acousticness (range: [0,1])

  • danceability (range: [0,1])

  • energy (range: [0,1])

  • duration_ms (range: [200000,300000])

  • instrumentalness (range: [0,1])

  • valence (range: [0,1])

  • popularity (range: [0,100])

  • tempo (range: [50,150])

  • liveness (range: [0,1])

  • loudness (range: [-60,0])

  • speechiness (range: [0,1])

Dummy:

  • mode: (0 = Minor, 1 = Major)

  • explicit: (0 = No explicit content and 1 = Explicit content)

Categorical:

  • key: keys on an octave encoded as integers in range [0,11] (C = 0, C = 1, etc.)

  • timesignature: predicted time signature.

  • artists: list of contributing artists.

  • artists: list of IDs of contributing artists.

  • release_date: date of release (yyyy-mm-dd).

  • name: track name.

The artists dataset (artists.csv) has the following features:

  • id: ID of the artist.

  • name: artist name.

  • followers: how many followers the artist has.

  • popularity: popularity of the artists based on their tracks.

  • genres: list of genres covered by the artist’s tracks.

We will follow the data science cycle (Data Exploration - Data Preparation - Data Modeling - Model Evaluation - Model Deployment) to solve this problem.

Initialization

This example uses the following version of VerticaPy:

import verticapy as vp

vp.__version__
Out[2]: '1.1.0'

Start by importing VerticaPy and loading the SQL extension, which allows you to query the Vertica database with SQL.

%load_ext verticapy.sql
The verticapy.sql extension is already loaded. To reload it, use:
  %reload_ext verticapy.sql

Connect to Vertica. This example uses an existing connection called VerticaDSN. For details on how to create a connection, see the Connection tutorial. You can skip the below cell if you already have an established connection.

vp.connect("VerticaDSN")

Create a new schema, “spotify”.

vp.drop("spotify", method = "schema")
Out[4]: True

vp.create_schema("spotify")
Out[5]: True

Data Loading

Load the datasets into the vDataFrame with read_csv() and then view them with head().

# load datasets as vDataFrame objects
artists = vp.read_csv("artists.csv", schema = "spotify", parse_nrows = 100)
tracks  = vp.read_csv("tracks.csv" , schema = "spotify", parse_nrows = 100)

# Display
artists.head(100)
tracks.head(100)
Abc
id
Varchar(44)
100%
...
Abc
Varchar(56)
100%
123
popularity
Int
100%
1004reCzVFOidvBuYrYia9Y...27
200ekfPE5ZS3NwF8H8o8GBk...47
301TgMAgIALWvVXlKjUwpfn...9
402Cq85QmaYHDi4dW7AxTRZ...9
502ESuuto8Jwyo4PeiJ1Xim...1
603FgbE2vKKVEFBFHi8IfJG...55
703KLzHVK6la8dVop1iVI5x...50
803jLJnyfZXs1ssrIALfGRm...22
904Lio76CKJCMPbK5hV6J4w...5
1005AVHcWP9DF6y6LEU845uz...15
1105Fgqq7GfWeNol1TR5H3og...35
1205UsyksBcAUVdfyREMxbDm...11
13070tdNOiP3pIsGlqNfVkG3...51
140CgCy79P84g1meaXcwwFqZ...0
150CsrftI3Zs3nvfSW6MRglc...0
160EPzUAW8kwuPedmmVP6n9S...53
170EYfWGAHPugeWUKKvoMU79...4
180EvkY8O19trlgsfrVOTQgg...31
190FbccBQBb69lfv4arbt6kX...36
200GPfyyiTlLdG6rQthueRBM...26
210Gk98lHv6LlqbWPwdMiga2...50
220GsCeqHAG63k8CRj1NH8e4...0
230HTub0NhKSRgggtmJBP9aR...0
240HZL4dV60t13CHasIHwaLP...4
250HhejlCvg1WCO9nXNZGEkc...1
260Id5ZU9SxHcgE32nfJMTbh...19
270KTn3DOb57GcGjPoA09ABL...0
280KZLEvrZHdqVDKdclXRVK0...0
290KirHnU7pIfeMYWSJ6xm8I...12
300LcUNEKY8mVqNmYfrgZrxl...24
310M5UiR76X2ybfo6N9iVNWr...36
320MQyjuZSqwUlAmWo7bryry...8
330MXVKY88dOygNxQSjYAiCn...9
340MzWXIO3Z73PfIwg0UUGHm...36
350NMfHNHHyEUp2DZxIXyA0c...6
360NiIlOoQCQPrri3Mnzb41D...43
370OWXK55YvtWja5pKp8vqXL...17
380PbRHFtbXsxQfOHl6m86dd...32
390PtPpPyhP8KRgSwYDvhPlE...2
400Qo6PIs38oznVHFxs9WU0N...37
410RLhLvcRdN4LsXUvRsL9rM...31
420T0rADxhl1CLxhSS3t4FJr...40
430T0yIgJd9ZIeVJqZw276iH...6
440TdjP78ddOnKTEVuF3LBrT...30
450UCbjuZR3UYm9XtycKimCz...20
460UvDCk7VwDQ0JWGeTTaGpB...0
470VJDqh2nHbXaafoaDbecop...6
480VeG3URJkBzh0CHJqZqmPL...18
490VhKsa9J4dkGjkSHwthPUl...4
500W0udbffr9z2chRB5eiq9W...2
510WDJa0qnagyOnMaiD26wht...65
520WKQQ0JVwqVfaCUeocL81k...0
530WRMNsx6J1XPOIYhxs2ZQy...0
540WX7MXOUx7elCFdxdgvdBU...43
550XqtseX3XzxM4MwPlVLDBJ...3
560XuTvNiI3kFV0Jpt5MTaMf...0
570Y0MpkBrGD02Cx3Mmhfa9I...42
580aquWYdumRf3dKccHKaQ25...11
590bbSbmpfnelM4nQfogVk9B...0
600bfBH61NEvZOmVTUyUL1yO...40
610buOvM3DxsknoPmgGufB5B...0
620c7soAA3Nv5aaDBMtCy7v4...27
630cFQLs28WprLqNslPA1vBH...8
640fgRKFA5ecwbbU6u6nSh2J...9
650g8uDsDCthOato1OArQG4d...16
660hmW4jgeM8oo7PrTja7KzY...9
670jEJGHxA3gkLdjviT1H0wk...40
680kh9Gvy9lGZsq84x7I37DC...64
690lKCO7SCRiTCS4ZEU6l1zx...50
700lTe15Ofpyg39nXvLNAfcm...29
710lUa6o7QrvyJKszdaBnoAH...45
720mGMdkeDynbGXSVd0PY8Oq...49
730mHLX59DLqrZHIo8mOhiBG...23
740mneo6UHjcOtZBm1Tw8t67...6
750pUbw5HdKXcSz6luKNyOiR...6
760q4Dul29Bz5se2iYcKPQXA...1
770qI3BHXdNAjGHu3NqRaacs...2
780sgX7YgncTMJs40ANv08V2...3
790sijpMmVF9ui4micUVn9d6...30
800tLpQEO7WioDR2cjo3SgWj...0
810tMBk4ZobHzjogZ2911v6y...5
820tmTvcGSgfSn6dLFNxC3Sd...36
830xBUHtBX1kujjjbayPiYOq...2
840z0ey55umWNi8s2HsNaGxB...1
850zrHcdKNA1olsruclYjy7h...19
8610HoQ9uLWCt4YoiaYkmQVU...27
8710ki5CunLQjvMB093EClw3...1
8811mE3U9BBkGQcOty6rOXUw...6
8912C8rZ4SY6WxvuxqykeKw6...4
9012fqDmzvnOz7oQE5J2XDfL...0
91138bq70hDEiUOtwGFRKB9M...23
9213BySi42Trub84QyO83Q6c...0
93154o9Wi0JpSDbYQPMtmpd5...57
9415kkqvIcypRQGUiE17Shej...17
95161n5VNTH8MQ8hh5jgwewr...2
9616E01HKHSbeo1s2GB0zam5...13
9716IE8lpWA2U3bfB4kumGzW...45
9816TsNPlesuA1R9kPLS6nta...58
99177K31OvglpG0Epy24mWyd...8
10017CHv5PPFzmVshxqUpGEdZ...17
Abc
id
Varchar(44)
100%
...
123
tempo
Numeric(10,5)
100%
123
time_signature
Int
100%
100DK20f7WgF2t1eTwlveGu...132.384
200LQ1LG1WCfyYEgyg17ZeK...107.914
300MAWNbKDCBUMjqmyx7ipK...72.4914
400PPopDfcYt0QOu8vxMqm5...116.3894
500PVvXOTonYJRSItustZhr...80.5664
600a8mZIrYshErBYRiY6J7d...50.6884
700iXlZ8jMVxSrxXvQomYU1...92.2174
800kTY2h5rdJwP600uhCDkT...109.4944
900qTlNBM62qVriou9qedWC...109.9241
10012eyTyyRCHaWTOhVGVMPR...106.7764
11017tyGFxZEvwmaZ65Gjt3v...209.8414
1201AaVwdRjbSIvolvCifrv5...67.5334
1301JPQ87UHeGysPVwTqMJHK...99.9674
1401gpCBvSLYcpMjbLPykc1B...133.7165
1501ie0dwhOQIvsSi6BKAbv8...120.7094
1601kKvDxBNQ6WZNQSpSWPyV...165.8264
17021jmGJUrMmS92WnBWvoAC...84.945
1802cC87YyLKhCQjU8IH1Frm...79.9834
1902lI7Q5IBgWpl4CNiB5k8h...147.3193
20036VdTP0ggdePwbvbFuT8w...172.8723
2103AMfxhiuPDtqVBUGdzFOU...79.2844
2203HF18PyO2aob3htRSW0js...80.9824
2303L76Pappow8L5V2fK71wk...84.1464
2403MB4uIPXZJ63F1HQk0Bns...71.563
2503TIKoaX56q27pRyYZ9zPe...137.5114
2603YZ3SmELE9LlojJG3GgEl...118.8414
2703w0JAy22hxUDRKLNPradE...116.3264
2804N18CfIbOJPnVLGOKgJNB...89.1284
2904RedUeDN5QjRSJveIz6vp...137.6645
3004wUuScZcskGGpnSEt5Vmb...96.0854
31058A8NFVMcfRphgOpxXH9M...130.9234
3205CBEqQ8CWilFUNW7vYPi9...72.7534
3305CM8z8uYj064zvn6YOAHj...88.2924
3405E60GLnBS8eMhcAsIbfpM...122.7624
3505T5cnFmAXiiBWV5wtM3tX...113.175
3605UEdT06NxdH4JTgltDnaw...76.4384
3705r5QhWMq0T0EQDhYZouLO...80.5544
38064WS0WvMzTXKl0xx45ryF...102.0664
3906FoaUqbBSpO4crPQ1GXrF...86.6154
4006GtF3borSXnkXfCyeUUmX...118.7734
4106LtO6ogcuQktXe0pQghdh...125.2963
4206NEuecnoNSQxDC3lfHpyy...185.934
4306XszKvYdMfzwpoxHDnpun...109.2411
4406uDJ8qAO6btD89f4aMlEk...99.8844
4507KD6sXOexgCnzd2ux4uTe...120.6444
4607UmbTUVteqSGrFZkMdBvG...188.354
4707XHoD2XHmzgxGHhWw7fLx...116.4614
4807c11BxeE28llfHSPSRqFv...85.0524
4907kKdEH4zfobUFfahc7RcW...66.654
5007rAh606hDOjnTaHlJoT0C...131.9261
5107ubKGIpVTG49naAmpe8gt...62.2693
520874YaGRpvsVw92BDQZr2A...165.5643
5308CVQP4qjws8a6DtNG3aVK...162.4073
5408SpaYq4TuWsuqHoxJg3xM...116.6614
5508kHpj5FOW7LjNyVBz5Zvs...142.1545
5608lHFwOBB8FnLwzuGwYMEh...145.9954
5708rO8bafPdrmQssvsAMxVl...85.3574
5808tC0ditym84D3NBG0jQqA...78.0734
59093IYCNJHNy1cvAH0Mvkch...76.0644
6009Qxk2wRRsicvUtcLvtSTo...109.4583
6109aFzJT56dpsZfN8O2oR6U...183.6155
6209bZ7ATMlxA7LNjCCoCzdA...120.0214
6309dn63xIPf3n72HNizEElm...207.6724
640A2BKRDiiNpiRYtiOSajYX...81.2084
650ABNJ6silC7NHkocdiUxTF...70.8064
660ACKq3I2568VVrgcpwFkOI...77.0694
670AgnrqcD0dr22vaZeroGOX...89.3724
680AjhUemO2FH31c4Bda00tO...65.7673
690AwtRpubA0XprHmhMkeFQf...111.2554
700B4HWkQHhAzxW43V7Z0fQO...120.6244
710B6RBLFwXxp4CqOJ02SqdT...85.4164
720BNYfaz6wi8hwz2yWfGciG...118.9674
730BZK5owhrgOW72f0CAjDYB...150.3483
740BqR2YNnnicAYkkNFkHLvR...143.7333
750CGFSEHPCoxIql2t8D5Kjc...173.4134
760CK9b2dEDscskyQ0Xd7L4p...169.9374
770Cbra96lST7uHGZjSpfR46...152.6924
780Cmq2tmC0QGFVgliknr0MN...128.5165
790CrCsWktPpP2yO8hSKnCsy...91.3974
800D12JQoW6ljmJM86pgrJ1z...90.684
810D1pEisM3QkiacGXJe5dmd...122.9094
820DA0LWjbdBTmrlKr2CmQ62...117.5134
830DXiJ6NXLtD5SRzsOHPQPI...120.3064
840DplGIOdpwkzdwKbOClMlJ...172.7613
850DtqOgGu29ZUPyhlRdB54g...100.474
860Dw6qWZblJ3cUKBobHHGlK...132.8234
870E0nqXEw6HtFEqsQTTOMcJ...100.0114
880E2UM5021iM76L4zvuIsGK...96.6034
890EAJ0xn42jBzzD9BRJA0I8...114.7834
900EDwfMkuOLWqZmEiYrAVIi...108.5235
910EOYB5Dhk3YeLrjvajYiIj...86.9644
920EPBPqrmLxVl3UXzcJuqVj...80.7724
930EQufZ7uB8mcnISIKfQtyJ...118.4254
940ESg82SK2vuKfd8xfZ0f17...102.4364
950EcQqsrUkVhPZsPc7InZwS...117.783
960Eh0NJcwxUrtIsyjsNpv1e...87.2713
970EzFMcpJw2BaOpq4klnCbf...97.2853
980F24HA5PftFkfETekL0cwm...123.2914
990FA6uDYBkJ0yqiZwQZHwgg...139.8784
1000FVH9hZ1Xn1jgl0ehI5wjh...125.4844

Warning

This example uses a sample dataset. For the full analysis, you should consider using the complete dataset.

Since we are only focusing on Polish artists in this subset of data, let us save it in the database with the proper name.

polish_artists = artists

# save it to the database
polish_artists.to_db('"spotify"."polish_artists"', relation_type = "table")

Data Exploration

We can visualize the top 60 most-followed Polish artists with a bar chart.

# make a highchart of the top 50 most-followed Polish artists
polish_artists.bar(
    ["name"],
    method = "mean",
    of = "followers",
    max_cardinality = 50,
    width = 800,
)

We can do the same with the most popular tracks. For example, we can graph Monika Brodka’s most popular tracks like so:

# find Monika Brodka's songs
brodka_tracks = tracks.search("artists ilike '%brodka%'")

# plot Brodka's tracks ordered by popularity
brodka_tracks.bar(
    ["name"],
    method = "mean",
    of = "popularity",
    max_cardinality = 25,
    width = 800,
)

To get an idea of what makes Monika Brodka’s songs popular, let’s create a boxplot of the numerical feature distribution of her tracks.

## list of the relevant numerical features
numerical_features = [
    "danceability",
    "energy",
    "speechiness",
    "acousticness",
    "instrumentalness",
    "valence",
    "liveness",
]

# create a boxplot of the above features
brodka_tracks.boxplot(columns = numerical_features)

Timing is a classic factor for success, so let’s look at the popularity of Monika’s songs over time with a smooth curve.

# extract year from the date
brodka_tracks["release_year"] = "year(release_date::date)"

# smooth the popularity using rolling mean
brodka_tracks.rolling(
    func = "mean",
    columns = "popularity",
    window = (-3, 3),
    order_by = "release_year",
    name = "smoothed_popularity",
)

# plot the smoothed curve for popularity of her songs
brodka_tracks.plot(ts = "release_date", columns=["smoothed_popularity"])

Numerical-feature Analysis

Bringing it all together, let’s try to get an idea of how these numerical features change and correlate with each other in Monika’s most popular songs.

# extract year from date
tracks["release_year"] = "year(release_date::date)"

# get the average of numerical features during the year
yearly_aggs = tracks.groupby(
    "release_year", [
        "AVG(danceability) as danceability",
        "AVG(energy) as energy",
        "AVG(speechiness) AS speechiness",
        "AVG(acousticness) AS acousticness",
        "AVG(instrumentalness) AS instrumentalness",
        "AVG(valence) AS valence",
        "AVG(liveness) AS liveness",
    ]
)


# plot the cures for numerical features along the different years
yearly_aggs.plot(
    ts = "release_year",
    columns = numerical_features,
)
# correlation of numerical features
tracks[tracks[numerical_features]].corr()

Feature Engineering

To expand our analysis, let’s take into account some descriptive features. Since our goal is to predict popularity, some useful features might be:

  • number of followers

  • popularity for the artist of the track

  • the number of artists per track

Additionally, we manipulate our data a bit to make things easier later on:

  • converting the duration unit from ms to minute

  • extracting the year from the date.

%%sql
DROP TABLE IF EXISTS spotify.polish_tracks;
CREATE TABLE spotify.polish_tracks AS
SELECT * FROM spotify.tracks
WHERE id_artists IN (SELECT t.id_artists FROM spotify.tracks t JOIN spotify.polish_artists p
                    ON t.id_artists LIKE '%' || p.id || '%');
CREATE TABLE spotify.polish_tracks_clean AS
SELECT
    x.*,
    x.duration_ms / 60000 AS duration_minute,
    x.release_date::date AS release_year,
    y.followers AS artists_followers,
    y.popularity AS artist_popularity
FROM spotify.polish_tracks AS x LEFT JOIN spotify.artists AS y
ON x.id_artists LIKE '%' || y.id || '%';
polish_tracks = vp.vDataFrame("spotify.polish_tracks_clean")

# count the number of artists per track
polish_tracks.regexp(
    column = "artists",
    pattern = ",",
    method = "count",
    name = "nb_singers",
)
polish_tracks["nb_singers"].add(1)
Abc
id
Varchar(44)
100%
...
123
artist_popularity
Int
100%
123
nb_singers
Integer
100%
100K3yuFVb33hBNoEyWcdnj...531
204PxH7CFGAaAvo6j0zZAOr...523
304PxH7CFGAaAvo6j0zZAOr...503
404PxH7CFGAaAvo6j0zZAOr...453
506kPUkFJv1RfEVjgEF3oRz...471
607FeXj3ssNHhy86oX5IIKI...531
70ESg82SK2vuKfd8xfZ0f17...471
80H9SRjTU6FJ1wJ5DMtR6wV...672
90H9SRjTU6FJ1wJ5DMtR6wV...682
100KfikiN9pPQ80nIOgvGGkd...531
110SwAC0yvUxGq2frHlNCfvE...531
120YZFbFNA1r8Q649MAaAy1C...531
130byNwHtvPr2vIO9cbwyZjH...471
140geug0mKXXEuE0VEvoZ8kp...471
150jbwghDbZd9dHrUa4oHakc...531
160kXr0uVZTAzjWywvWBkFXf...531
170kzWBjzgUuzRAquOp5L9BR...471
1815LfHBNBrTKHGce4SNtOR6...522
1915LfHBNBrTKHGce4SNtOR6...452
201OWseQo2xtPDNxFAiAVyDu...471

Define a list of predictors and the response, and then save the normalized version of the final dataset to the database.

# define predictors and response
predictors = [
    "duration_minute",
    # "release_year",
    "danceability",
    "energy",
    "loudness",
    "speechiness",
    "acousticness",
    "instrumentalness",
    "liveness",
    "valence",
    "artists_followers",
    "artist_popularity",
    "nb_singers",
]
response = "popularity"
polish_tracks.normalize(
    method = "minmax",
    columns = predictors,
)
# save the final dataset to the database
vp.drop("spotify.polish_tracks_data_final")
polish_tracks.to_db('"spotify"."polish_tracks_data_final"', relation_type = "table")

Machine Learning

We can use AutoML to easily get a well-performing model.

# define a random seed so models tested by AutoML produce consistent results
vp.set_option("random_state", 2)

AutoML automatically tests several machine learning models and picks the best performing one.

from verticapy.machine_learning.vertica.automl import AutoML

# define the model
auto_model = AutoML(
    "spotify.automl_spotify_polish",
    estimator = "fast",
    preprocess_data = True,
    stepwise = False,
    cv = 2,
)

Train the model.

auto_model.fit(
    "spotify.polish_tracks_data_final",
    predictors,
    response
)

Starting AutoML


Testing Model - LinearRegression

Model: LinearRegression; Parameters: {'tol': 1e-06, 'max_iter': 100, 'solver': 'newton'}; Test_score: 8.03653061563296; Train_score: 7.6086948476505; Time: 2.5748002529144287;
Model: LinearRegression; Parameters: {'tol': 1e-06, 'max_iter': 100, 'solver': 'bfgs'}; Test_score: 8.03652451786366; Train_score: 7.60869496994112; Time: 7.14464271068573;
Grid Search Selected Model
LinearRegression; Parameters: {'tol': 1e-06, 'max_iter': 100, 'solver': 'bfgs', 'fit_intercept': True}; Test_score: 8.03652451786366; Train_score: 7.60869496994112; Time: 7.14464271068573;

Testing Model - ElasticNet

Model: ElasticNet; Parameters: {'tol': 1e-06, 'max_iter': 100, 'solver': 'cgd', 'C': 1.0, 'l1_ratio': 0.5}; Test_score: 8.400499108916339; Train_score: 8.31347605013778; Time: 3.1259127855300903;
Grid Search Selected Model
ElasticNet; Parameters: {'tol': 1e-06, 'C': 1.0, 'max_iter': 100, 'solver': 'cgd', 'l1_ratio': 0.5, 'fit_intercept': True}; Test_score: 8.400499108916339; Train_score: 8.31347605013778; Time: 3.1259127855300903;

Testing Model - Ridge

Model: Ridge; Parameters: {'tol': 1e-06, 'max_iter': 100, 'C': 1.0}; Test_score: 8.02461535485319; Train_score: 7.60920752917329; Time: 2.7998028993606567;
Grid Search Selected Model
Ridge; Parameters: {'tol': 1e-06, 'C': 1.0, 'max_iter': 100, 'solver': 'newton', 'fit_intercept': True}; Test_score: 8.02461535485319; Train_score: 7.60920752917329; Time: 2.7998028993606567;

Testing Model - Lasso

Model: Lasso; Parameters: {'tol': 1e-06, 'max_iter': 100, 'solver': 'cgd', 'C': 1.0}; Test_score: 8.375940164726595; Train_score: 8.060900854713466; Time: 2.9285014867782593;
Grid Search Selected Model
Lasso; Parameters: {'tol': 1e-06, 'C': 1.0, 'max_iter': 100, 'solver': 'cgd', 'fit_intercept': True}; Test_score: 8.375940164726595; Train_score: 8.060900854713466; Time: 2.9285014867782593;
Final Model

Ridge; Best_Parameters: {'tol': 1e-06, 'C': 1.0, 'max_iter': 100, 'solver': 'newton', 'fit_intercept': True}; Best_Test_score: 8.02461535485319; Train_score: 7.60920752917329; Time: 2.7998028993606567;
auto_model.plot()

Extract the best model according to AutoML. From here, we can look at the model type and its hyperparameters.

# extract the model type and hyperparameters
best_model = auto_model.best_model_

bm_type = best_model._model_type

hyperparams = best_model.get_params()

print(bm_type)
Ridge

print(hyperparams)
{'tol': 1e-06, 'C': 1.0, 'max_iter': 100, 'solver': 'newton', 'fit_intercept': True}

Thanks to AutoML, we know best model type and its hyperparameters. Let’s create a new model with this information in mind.

from verticapy.machine_learning.vertica import LinearRegression

# define the model
rf_model = LinearRegression("spotify.linear_regression_spotify", **hyperparams)

# train the model
rf_model.fit(polish_tracks, predictors, response)

# use the model to predict
rf_model.predict(
    polish_tracks,
    name = "estimated_popularity",
)
Abc
id
Varchar(44)
100%
...
Abc
Varchar(196)
100%
123
estimated_popularity
Float(22)
100%
100K3yuFVb33hBNoEyWcdnj...32.687515815542
204PxH7CFGAaAvo6j0zZAOr...42.3585385142475
304PxH7CFGAaAvo6j0zZAOr...42.0822912902126
404PxH7CFGAaAvo6j0zZAOr...38.7309512422115
506kPUkFJv1RfEVjgEF3oRz...28.1637982132495
607FeXj3ssNHhy86oX5IIKI...23.7486119838123
70ESg82SK2vuKfd8xfZ0f17...23.7995214149152
80H9SRjTU6FJ1wJ5DMtR6wV...56.0540426695094
90H9SRjTU6FJ1wJ5DMtR6wV...58.8944789378883
100KfikiN9pPQ80nIOgvGGkd...34.2927702242878
110SwAC0yvUxGq2frHlNCfvE...42.0160017131988
120YZFbFNA1r8Q649MAaAy1C...28.5560009504502
130byNwHtvPr2vIO9cbwyZjH...31.3197072121662
140geug0mKXXEuE0VEvoZ8kp...31.5709660952455
150jbwghDbZd9dHrUa4oHakc...38.8080040064003
160kXr0uVZTAzjWywvWBkFXf...35.11050094137
170kzWBjzgUuzRAquOp5L9BR...29.1632205931634
1815LfHBNBrTKHGce4SNtOR6...43.2455014418643
1915LfHBNBrTKHGce4SNtOR6...39.6179141698283
201OWseQo2xtPDNxFAiAVyDu...30.3224018170893

View the regression report and the importance of each feature.

rf_model.regression_report()
value
explained_variance0.686560783508525
max_error23.2314079669432
median_absolute_error5.26887801846472
mean_absolute_error6.14061095243189
mean_squared_error58.2519664104863
root_mean_squared_error7.63229758398389
r20.686560783508526
r2_adj0.675432763988118
aic1454.82011140199
bic1502.92724625364
rf_model.features_importance()

To see how our model performs, let’s plot the popularity and estimated popularity of songs by other Polish artists like Brodka and Akcent.

# results for Brodka
polish_tracks.search(
        "LOWER(artists) LIKE '%brodka%'",
        usecols = [
            "popularity",
            "name",
            "estimated_popularity",
        ],
    ).plot(
    ts = "name",
    columns = ["popularity", "estimated_popularity"],
)
# results for Brodka
polish_tracks.search(
        "LOWER(artists) LIKE '%akcent%'",
        usecols = [
            "popularity",
            "name",
            "estimated_popularity",
        ],
    ).plot(
    ts = "name",
    columns = [
        "popularity",
        "estimated_popularity",
    ],
)

Group Artists using Track Features

While our tracks don’t have an explicit “genre” feature, we can approximate the effect by grouping artists based on their tracks’ numerical features.

Let’s start by taking the averages of these numerical features for each artist.

# group by artist
artists_features = polish_tracks.groupby(
    [
        "id_artists",
        "artists",
    ],
    expr=[
        "AVG(danceability) AS danceability",
        "AVG(energy) AS energy",
        "AVG(speechiness) AS speechiness",
        "AVG(acousticness) AS acousticness",
        "AVG(instrumentalness) AS instrumentalness",
        "AVG(valence) AS valence",
        "AVG(liveness) AS liveness",
    ]
)

# save relation to the database as "artists_features"
artists_features.to_db('"spotify"."artists_features"')
Abc
Varchar(364)
100%
...
123
valence
Float(22)
100%
123
liveness
Float(22)
100%
1...0.6024967148488830.214027245636441
2...0.44371441086290.157088122605364
3...0.6879106438896190.038527032779906
4...0.7919404292597460.063005534269902
5...0.3255713586354380.12835875090777
6...0.6769601401664480.22585598735024
7...0.4951817783618050.113452532992763
8...0.5762155059132720.291187739463602
9...0.2537231712658780.121434653043848
10...0.4305738063950940.35823754789272
11...0.5455540954883920.323116219667944
12...0.1535260621988610.112388250319285
13...0.3703460359176520.053533418475947
14...0.6145422689443720.090570455512984
15...0.4715286903197550.251947637292465
16...0.4173236968900570.154640272456364
17...0.3117608409986860.081364410387399
18...0.6813403416557160.097488292890592
19...0.4212658782303990.243827160493827
20...0.3002628120893560.154959557258408

Grouping means clustering, so we use an elbow() curve to find a suitable number of clusters.

from verticapy.machine_learning.model_selection import elbow

# define numerical features
preds = [
    "danceability",
    "energy",
    "speechiness",
    "acousticness",
    "instrumentalness",
    "liveness",
    "valence",
]


# elbow curve
elbow_curve = elbow(
    '"spotify"."artists_features"',
    preds,
    n_cluster = (1, 20),
    show = True,
)

elbow_curve

Let’s define and use the Vertica KMeans algorithm to create a model that can group artists together.

from verticapy.machine_learning.vertica.cluster import KMeans

# define k-means
model = KMeans(
    '"spotify"."KMeans_spotify"',
    n_cluster = 7,
)

We can train our new model on the artists_features relation we saved earlier.

# train the model
model.fit(
    '"spotify"."artists_features"',
    X = preds,
)



=======
centers
=======
danceability| energy |speechiness|acousticness|instrumentalness|liveness|valence 
------------+--------+-----------+------------+----------------+--------+--------
   0.74755  | 0.54774|  0.23642  |   0.20312  |     0.00549    | 0.09440| 0.35149
   0.45502  | 0.44870|  0.02991  |   0.22027  |     0.14763    | 0.15310| 0.52708
   0.76389  | 0.55595|  0.43462  |   0.26255  |     0.00187    | 0.11664| 0.68244
   0.52136  | 0.49604|  0.27668  |   0.38858  |     0.00547    | 0.15539| 0.26164
   0.60670  | 0.72327|  0.10514  |   0.10965  |     0.03246    | 0.26350| 0.51603
   0.75753  | 0.61068|  0.35731  |   0.67534  |     0.00002    | 0.14746| 0.68852
   0.50817  | 0.37596|  0.18257  |   0.88200  |     0.00000    | 0.08365| 0.00000


=======
metrics
=======
Evaluation metrics:
     Total Sum of Squares: 13.70887
     Within-Cluster Sum of Squares: 
         Cluster 0: 0.42703059
         Cluster 1: 0.79885754
         Cluster 2: 0.99103343
         Cluster 3: 1.6490998
         Cluster 4: 1.3303828
         Cluster 5: 1.008654
         Cluster 6: 0
     Total Within-Cluster Sum of Squares: 6.2050582
     Between-Cluster Sum of Squares: 7.5038122
     Between-Cluster SS / Total SS: 54.74%
 Number of iterations performed: 4
 Converged: True
 Call:
kmeans('"spotify"."KMeans_spotify"', '"spotify"."artists_features"', '"danceability", "energy", "speechiness", "acousticness", "instrumentalness", "liveness", "valence"', 7
USING PARAMETERS max_iterations=300, epsilon=0.0001, init_method='kmeanspp', distance_method='euclidean')

Plot the result of the k-means algoritm:

model.plot()
# predict the genres
pred_genres = model.predict(
    '"spotify"."artists_features"',
    X = [
        "danceability",
        "energy",
        "speechiness",
        "acousticness",
        "instrumentalness",
        "liveness",
        "valence",
    ],
    name = "pred_genres",
)

Let’s see how our model groups these artists together:

# observe the results
pred_genres["artists", "pred_genres"].sort({"pred_genres": "desc"})
Abc
Varchar(344)
100%
123
pred_genres
Integer
100%
16
25
35
45
55
65
75
85
95
105
114
124
134
144
154
164
174
184
194
204

Conclusion

We were able to predict the popularity Polish songs with a RandomForestRegressor model suggested by AutoML. We then created a KMeans model to group artists into genres (clusters) based on the feature-commonalities in their tracks.