Millón de Monos

Weblog de Manuel Aristarán

Cómo son los royalties que paga Spotify en cada país?

Lo siguiente es un ejercicio de análisis de un dataset de reproducciones emitido por el servicio Distrokid, para un artista argentino con 2.5 millones de reproducciones en los últimos 9 meses, en el servicio de streaming Spotify.

import pandas as pd
import altair as alt
import numpy as np

data = pd.read_csv('DistroKid_1558988581759.tsv', sep='\t', encoding='iso-8859-1')
sales = data[(data.Store == 'Spotify')]

Las columnas que nos interesan son:

Ingreso mensual

sales_per_month=sales.groupby(['Sale Month'])[['Quantity', 'Earnings (USD)']].sum().reset_index()

alt.Chart(sales_per_month).mark_bar().encode(
    x='Sale Month:O',
    y='Earnings (USD):Q',
    tooltip='Quantity:Q',
).properties(width=500, title="Ingreso (USD) por mes")\
 .configure(background='#FFFFFF')

png

Los meses de aumento en el ingreso corresponden a lanzamientos.

¿En qué países está la audiencia?

Por supuesto, nos interesa saber a dónde está la mayor cantidad de escuchas de nuestros tracks

by_country = sales.groupby('Country of Sale').sum().reset_index()
top_10_countries = by_country.sort_values('Quantity', ascending=False).head(10).reset_index()
top_10_countries[['Country of Sale', 'Quantity']]
Country of Sale Quantity
0 AR 1917972
1 CL 199124
2 UY 98114
3 US 37505
4 PE 31865
5 MX 30346
6 CO 14393
7 ES 12948
8 PY 10372
9 EC 9856

Vemos que Argentina es el país principal, muy por encima del resto de los países: un orden de magnitud superior al segundo puesto. Graficamos esta distribución en un gráfico con escala logarítmica en el eje vertical.

(Ya que estamos,agregamos las banderas de los países)

OFFSET = ord('🇦') - ord('A')
def flag(code):
    """ Retorna el emoji de la bandera de un país, dado su código ISO-3166 alpha-2"""
    return chr(ord(code[0]) + OFFSET) + chr(ord(code[1]) + OFFSET)

top_quantity = by_country.sort_values('Quantity', ascending=False).reset_index()
top_quantity.loc[:, 'country_name_and_flag'] = top_quantity['Country of Sale'].apply(lambda c: flag(c) + c)
alt.Chart(top_quantity.sort_values('Quantity', ascending=False)).mark_bar().encode(
    x=alt.X('country_name_and_flag:N', sort=list(top_quantity['country_name_and_flag'])),
    y=alt.Y('Quantity:Q',scale=alt.Scale(type='log')),
).properties(
    title='Number of streams per country (log scale)',    
)\
 .configure(background='#FFFFFF')

png

Promedio de pago por stream en cada país

Intentaremos, de manera muy poco rigurosa, estudiar la relación entre el importe promedio que Spotify paga al artista por cada reproducción, y el costo de la suscripción mensual en cada país.

Comenzamos calculando el valor promedio de cada reproducción (stream)

metrics = by_country[['Country of Sale', 'Quantity', 'Earnings (USD)']].sort_values('Quantity', ascending=False)
metrics = metrics.assign(per_stream_avg=metrics['Earnings (USD)']/metrics['Quantity'])
metrics[['Country of Sale', 'Quantity', 'per_stream_avg']].head(10)

Country of Sale Quantity per_stream_avg
2 AR 1917972 0.000965
11 CL 199124 0.001405
64 UY 98114 0.002186
63 US 37505 0.002802
50 PE 31865 0.001511
43 MX 30346 0.001261
12 CO 14393 0.001187
22 ES 12948 0.002075
54 PY 10372 0.001203
19 EC 9856 0.001744

Graficamos el promedio por reproducción para cada país

top_per_stream_avg = metrics.sort_values('per_stream_avg', ascending=False)
top_per_stream_avg.loc[:, 'country_name_and_flag'] = top_per_stream_avg['Country of Sale'].apply(lambda c: flag(c) + c)

alt.Chart(top_per_stream_avg).mark_bar().encode(
    x=alt.X('country_name_and_flag:N', type='nominal', sort=list(top_per_stream_avg['country_name_and_flag'])),
    y='per_stream_avg:Q',
    tooltip=['per_stream_avg:Q']
).properties(
    title='Per-stream payout average by Country\n(from a Spotify royalties report with %.2fM plays)' % (metrics.Quantity.sum() / 1e6)
)\
 .configure(background='#FFFFFF')

png

Suiza (CH) es el país que “mejor” paga por cada reproducción ($0.005 USD). Vemos también que los países en el tope del ranking pertecen al hemisferio norte. Gana peso nuestra hipótesis de que el per-stream average payout está relacionado con el costo de la suscripción al servicio Spotify

Costo mensual de Spotify en cada país

Necesitamos, por supuesto, una base de datos que contenga el costo de la suscripción a Spotify en cada país. Pensé en scrapearlo, pero por suerte un señor danés llamado Matias Singer construyó un Spotify International Pricing Index que contiene la información que necesitamos.

Los datos en la versión publicada están muy desactualizados, pero el buen Matias publicó el código fuente del scraper. Luego de unas modificaciones triviales, ejecuté ese código y obtuve la data que necesitaba.

spotify_monthly = pd.read_json('./spotify-pricing.jsonlines', lines=True)
spotify_monthly = spotify_monthly[spotify_monthly.convertedPrice > 0]
spotify_monthly.loc[:, 'country_upper']  = spotify_monthly.rel.str.upper()
spotify_monthly.head(5)
convertedPrice countryCode currency demonym internationalName link originalCurrency originalPrice originalRel price region rel subRegion title country_upper
0 4.990000 DZA DZD Algerian Algeria /dz-fr/premium/?checkout=false DZD USD 4.99/mois dz-fr 4.99 Africa dz Northern Africa Algérie (français) DZ
1 7.506266 CAN CAD Canadian Canada /ca-fr/premium/?checkout=false CAD 9,99 $ CAD/mois ca-fr 9.99 Americas ca Northern America Canada (français) CA
2 5.990000 GTM GTQ Guatemalan Guatemala /gt/premium/?checkout=false GTQ 5.99 USD al mes gt 5.99 Americas gt Central America Guatemala GT
3 5.990000 ECU USD Ecuadorean Ecuador /ec/premium/?checkout=false USD 5.99 USD al mes ec 5.99 Americas ec South America Ecuador EC
4 7.506266 CAN CAD Canadian Canada /ca-en/premium/?checkout=false CAD $9.99 CAD / month ca-en 9.99 Americas ca Northern America Canada (English) CA

Vamos a visualizar en un scatterplot las dos variables que nos interesan, valor promedio por reproducción y costo mensual del servicio.

El eje horizontal codificará el per-stream average payout, mientras que el vertical el costo mensual de la suscripción. El color de cada punto corresponde a la región del país, mientras que el tamaño codifica la cantidad de reproducciones.

Combinamos (merge) nuestro dataset de pago promedio por reproducción con el dataset obtenido por el scrapper, y construímos el scatterplot.

stream_avg_monthly_scatter = top_per_stream_avg.merge(spotify_monthly, left_on='Country of Sale', right_on='country_upper')
stream_avg_monthly_scatter.loc[:, 'convertedPriceFit'] = pd.Series(np.poly1d(fit)(stream_avg_monthly_scatter.per_stream_avg))

scatter = alt.Chart(stream_avg_monthly_scatter).mark_circle().encode(
    x='per_stream_avg:Q',
    y='convertedPrice:Q',
    tooltip=['internationalName:N', 'convertedPrice:Q', 'per_stream_avg:Q', 'Quantity:Q'],
    color='region:N',
    size=alt.Size('Quantity:Q', scale=alt.Scale(range=(50,1000))),
).properties(
title="Per-stream AVG payout VS Spotify monthly price by country (R²=%.2f)" % fit_rsq
)\
 .configure(background='#FFFFFF')
scatter

png

Vemos un atisbo de correlación lineal entre las dos variables. También vemos agrupaciones “horizontales”, que corresponden a importes como $5.99 (psychological pricing). Calculamos un fit lineal y también lo graficamos. También vamos a agregar líneas horizontales para enfatizar los países que comparten importes.

fit = np.polyfit(stream_avg_monthly_scatter.per_stream_avg, stream_avg_monthly_scatter.convertedPrice, 1)

fit_rsq = np.corrcoef(stream_avg_monthly_scatter.per_stream_avg, stream_avg_monthly_scatter.convertedPrice)[0,1]**2
scatter = alt.Chart(stream_avg_monthly_scatter).mark_circle().encode(
    x='per_stream_avg:Q',
    y='convertedPrice:Q',
    tooltip=['internationalName:N', 'convertedPrice:Q', 'per_stream_avg:Q', 'Quantity:Q'],
    color='region:N',
    size=alt.Size('Quantity:Q', scale=alt.Scale(range=(50,1000))),
).properties(
title="Per-stream AVG payout VS Spotify monthly price by country (R²=%.2f)" % fit_rsq
)

linear_fit = alt.Chart(stream_avg_monthly_scatter).mark_line().encode(
    x='per_stream_avg:Q',
    y='convertedPriceFit:Q'
)


# add fixed price points
fixed_price_points = pd.DataFrame(
    [
        {'text': '9.99€', 'rule': 11.20},
        {'text': '6.99€', 'rule': 7.83},
        {'text': '$5.99', 'rule': 5.99}
    ]
)

fixed_price_rules = alt.Chart(fixed_price_points).mark_rule(strokeDash=[1,1]).encode(
    y='rule:Q',
)

fixed_price_text = alt.Chart(fixed_price_points).mark_text(align='left', dy=-5, dx=5).encode(
    text='text:N',
    y='rule:Q',
    x=alt.X(value=0)
)

(scatter + linear_fit + fixed_price_rules + fixed_price_text).configure(background='#FFFFFF')

png

Qué averiguamos?

El resultado de este ejercicio informal y superficial sugiere que hay una relación entre el costo de la suscripción mensual al servicio Spotify, y lo que pagan a los artistas en concepto de royalties. También, el dato curioso de que Argentina es el país con el costo de suscripción más barato del mundo.

Si la relación que investigamos existe en efecto, podemos decir que pegar un hit en Argentina es un mal negocio en comparación al resto de los países.