wikimedica-disease-search/data/fetch/wikipedia_pageviews.py

138 lines
4.3 KiB
Python
Raw Normal View History

import collections
from datetime import datetime
from .http import session
import numpy
from scipy import stats
# Chemin racine pour les API Wikimedia
wikimedia_base_path = 'https://wikimedia.org/api/rest_v1'
# Patron daccès à lAPI pageviews de Wikimedia
wikimedia_pageviews_path = '/' + '/'.join([
'metrics', 'pageviews', 'per-article', '{project}',
'{access}', '{agent}', '{article}', '{granularity}',
'{start}', '{end}'
])
# Format de dates utilisée pour lAPI Wikimedia
wikimedia_date_format = '%Y%m%d'
# Date de première disponibilité des pageviews sur lAPI Wikimedia
wikimedia_pageviews_start = datetime(2015, 7, 1)
# Tableau contenant tous les jours de lannée de 1 à 365
year_all_days = numpy.arange(1, 366)
def _year_date_distance(a, b):
"""
Calcule la distance entre deux jours de lannée.
:example: _year_date_distance(10, 360) == 15
:example: _year_date_distance(numpy.array([10, 182, 355]), 182)
== [172, 0, 173]
:param a: Première valeur (peut être un tableau numpy).
:param b: Seconde valeur (peut être un tableau numpy).
:return: Valeur de la distance.
"""
return numpy.stack((
numpy.mod(a - b, len(year_all_days)),
numpy.mod(b - a, len(year_all_days))
)).min(axis=0)
def smooth(views, scale):
"""
Lisse un tableau de valeurs contenant une valeur par jour de lannée en
utilisant un noyau gaussien.
À la bordure de fin ou de début dannée, le lissage est fait avec lautre
morceau de lannée.
:param views: Données à lisser.
:param scale: Variance du noyau gaussien.
:return: Données lissées.
"""
ref_pdf = stats.norm.pdf(
_year_date_distance(year_all_days, 1),
scale=scale
)
pdf_matrix = numpy.stack([
numpy.roll(ref_pdf, day - 1)
for day in year_all_days
])
return pdf_matrix.dot(views)
def get(project, article):
"""
Obtient le nombre de visites sur une page Wikipédia par jour.
:param project: Projet Wikipédia ciblé.
:param article: Article ciblé dans le site.
:return: Compteur associant chaque jour à son nombre de visites.
"""
# Soumet une requête à lAPI REST pour obtenir les vues de larticle
res = session.get(wikimedia_base_path + wikimedia_pageviews_path.format(
project=project,
article=article,
access='all-access',
agent='user',
granularity='daily',
start=wikimedia_pageviews_start.strftime(wikimedia_date_format),
end=datetime.today().strftime(wikimedia_date_format)
))
data = res.json()
# Vérifie que la réponse reçue indique un succès
if res.status_code != 200:
if 'detail' in data:
detail = data['detail']
message = ', '.join(detail) if type(detail) == list else detail
raise Exception(message)
else:
raise Exception('Erreur {}'.format(res.status_code))
# Construit le dictionnaire résultant
return collections.Counter(dict(
(record['timestamp'][:8], record['views'])
for record in data['items']
))
def mean(views):
"""
Calcule les vues moyennes par jour de lannée à partir dun ensemble de
vues enregistrées (omettant le 29 février pour les années bissextiles).
:param views: Vues enregistrées par date.
:return: Tableau de taille 365 contenant le nombre de visites moyennes pour
chaque jour de lannée.
"""
# Fait la moyenne pour chaque jour hormis le 29 février
accumulator = {}
datemonth_format = '%m%d'
for day in range(1, 366):
datemonth = datetime.fromordinal(day).strftime(datemonth_format)
accumulator[datemonth] = []
for date_str, views in views.items():
date = datetime.strptime(date_str, wikimedia_date_format)
if not (date.month == 2 and date.day == 29):
datemonth = date.strftime(datemonth_format)
accumulator[datemonth].append(views)
for datemonth, value in accumulator.items():
accumulator[datemonth] = sum(value) / len(value) if value else 0
# Rassemble les valeurs moyennes pour chaque jour dans l'ordre de l'année
return [item[1] for item in sorted(
list(accumulator.items()),
key=lambda x: x[0]
)]