from jyquickhelper import add_notebook_menu
add_notebook_menu()
Commencez par télécharger la base de donnée :
twitter_for_network_100000.db
https://drive.google.com/file/d/0B6jkqYitZ0uTWjFjd3lpREpFYVE/view?usp=sharing
Vous pourrez éventuellement télécharger la base complète (3,4 millions d'utilisateurs, plutôt que 100000) ultérieurement si vous souhaitez tester vos fonctions. Ne perdez pas de temps avec ceci dans ce tp.
twitter_for_network_full.db
https://drive.google.com/file/d/0B6jkqYitZ0uTWkR6cDZQUTlVSWM/view?usp=sharing
Vous pouvez consulter l'aide de pytoolz (même interface que cytoolz) ici :
http://toolz.readthedocs.org/en/latest/
La section sur l'API est particulièrement utile car elle résume bien les différentes fonctions : http://toolz.readthedocs.org/en/latest/api.html
Ensuite exécutez la cellule suivante :
Liens alternatifs :
import pyensae.datasource
pyensae.datasource.download_data("twitter_for_network_100000.db.zip")
['.\\twitter_for_network_100000.db']
import cytoolz as ct
import cytoolz.curried as ctc
import sqlite3
import pprint
try:
import ujson as json
except:
import json
conn_sqlite = sqlite3.connect("twitter_for_network_100000.db")
cursor_sqlite = conn_sqlite.cursor()
Nous nous intéresserons à 3 tables : tw_users, tw_status et tw_followers_id.
La première (tw_users) contient des profils utilisateurs tels que retournés par l'api twitter (à noter que les profils ont été "épurés" d'informations jugées inutiles pour limiter la taille de la base de donnée).
La deuxième (tw_status) contient des status twitter (tweet, retweet, ou réponse à un tweet), complets, issus d'une certaine catégorie d'utilisateurs (les tweets sont tous issus d'environ 70 profils).
La troisième (tw_followers_id) contient des listes d'id d'users, qui suivent les utilisateurs référencés par la colonne user_id. Là encore ce ne sont les followers que de environ 70 profils. Chaque entrée contient au plus 5000 id de followers (il s'agit d'une limitation de twitter).
Elles ont les structures suivantes :
Les trois possèdent un champ content, de type json, qui sera celui qui nous interessera le plus. Vous pouvez accédez aux données dans les tables avec les syntaxes suivantes (vous pouvez commenter/décommenter les différentes requêtes).
# cursor_sqlite.execute( "SELECT user_id, content FROM tw_followers_id")
cursor_sqlite.execute( "SELECT id, content, screen_name FROM tw_users")
# cursor_sqlite.execute( "SELECT id, content, user_id FROM tw_status")
for it_elt in cursor_sqlite:
## do something here
pass
# ou, pour accéder à un élément :
cursor_sqlite.execute( "SELECT id, content, screen_name FROM tw_users")
cursor_sqlite.fetchone()
(1103159180, '{"utc_offset": 7200, "friends_count": 454, "entities": {"description": {"urls": []}, "url": {"urls": [{"expanded_url": "http://www.havas.com", "display_url": "havas.com", "indices": [0, 22], "url": "http://t.co/8GcZtydjWh"}]}}, "description": "Havas Group CEO", "id": 1103159180, "contributors_enabled": false, "geo_enabled": false, "name": "Yannick Bollor\\u00e9", "favourites_count": 873, "verified": true, "protected": false, "created_at": "Sat Jan 19 08:23:33 +0000 2013", "statuses_count": 654, "lang": "en", "time_zone": "Ljubljana", "screen_name": "YannickBollore", "location": "", "id_str": "1103159180", "url": "http://t.co/8GcZtydjWh", "followers_count": 7345, "listed_count": 118, "has_extended_profile": false}', 'YannickBollore')
Toutefois les curseurs de base de donnée en python se comportent comme des "iterables" (i.e. comme une liste ou une séquence, mais sans nécessairement charger toutes les données en mémoire).
On peut donc les passer directement en argument aux fonctions de cytoolz.
cursor_sqlite.execute( "SELECT user_id, content FROM tw_followers_id")
print( ct.count( cursor_sqlite ) )
cursor_sqlite.execute( "SELECT id, content, user_id FROM tw_status")
print( ct.count( cursor_sqlite ) )
cursor_sqlite.execute( "SELECT id, content, screen_name FROM tw_users")
print( ct.count( cursor_sqlite ) )
2079 16092 100071
Attention au fait que le curseur garde un état.
Par exemple exécutez le code suivant :
cursor_sqlite.execute( "SELECT user_id, content FROM tw_followers_id")
print( ct.count( cursor_sqlite ) )
print( ct.count( cursor_sqlite ) )
2079 0
Le deuxième count renvoit 0 car le curseur se rappelle qu'il est déjà arrivé à la fin des données qu'il devait parcourir. Il faut donc réinitialiser le curseur :
cursor_sqlite.execute( "SELECT user_id, content FROM tw_followers_id")
print( ct.count( cursor_sqlite ) )
cursor_sqlite.execute( "SELECT user_id, content FROM tw_followers_id")
print( ct.count( cursor_sqlite ) )
2079 2079
On peut également mettre la commande execute à l'intérieur d'une fonction, que l'on appelle ensuite :
def get_tw_followers_id():
return cursor_sqlite.execute( "SELECT user_id, content FROM tw_followers_id")
print( ct.count( get_tw_followers_id() ) )
print( ct.count( get_tw_followers_id() ) )
2079 2079
La commande exécute en elle-même ne prend pas du tout de temps, car elle ne fait que préparer la requête, n'hésitez donc pas à en mettre systématiquement dans vos cellules, plutôt que de risquer d'avoir un curseur dont vous ne vous souvenez plus de l'état.
Trouvez la liste des user_id différents dans la table tw_followers_id, en utilisant les fonctions cytoolz.
La fonction qui pourra vous être utiles ici :
ct.unique(seq)
=> à partir d'une séquence, renvoit une séquence où tous les doublons ont été supprimésVous vous rappelez sans doute que nous utilisions systématiquement pluck et map pour les exemples du cours, ceux-ci ne sont pas nécessaires ici.
A noter qu'il faudra sans doute utilisez la fonction list( ... ), ou une boucle for pour forcer l'évaluation des fonctions cytoolz.
import cytoolz as ct
import cytoolz.curried as ctc
list( ct.unique( cursor_sqlite.execute( "SELECT user_id FROM tw_followers_id") ) )
[(18814998,), (26031424,), (96093611,), (24906326,), (17211968,), (17172319,), (14658057,), (427288938,), (105116603,), (95533081,), (24732180,), (17508660,), (38170599,), (83784709,), (22428439,), (551669623,), (14389177,), (26073581,), (360680603,), (104857771,), (1055021191,), (217749896,), (2445174992,), (258345629,), (20702279,), (1134643441,), (415498269,), (374184532,), (1976143068,), (520449734,), (483087012,), (80820758,), (69255422,), (413839619,), (34225599,), (38021678,), (472412809,), (219361536,), (49969223,), (359979086,), (72276456,), (69032746,), (490126636,), (93484066,), (308679667,), (70385068,), (19438626,), (2709830497,), (120003933,), (61903470,), (273341346,), (146423935,), (601970599,), (3301529297,), (3026614773,), (3115155587,), (1374567409,), (2336714149,), (873851912,), (17744075,), (283979861,), (2310376344,), (88884666,), (417340481,), (274924087,), (534135763,), (16222469,), (497110024,), (299722946,), (271892968,), (481097138,), (79145543,), (988830782,)]
A noter que si vous voyez apparaître vos résultats sous la forme (79145543,), c'est normal, le curseur sqlite renvoit toujours ces résultats sous forme de tuple : (colonne1, colonne2, colonne3, ...) et ce même si il n'y a qu'une seule colonne dans la requête.
Nous utiliserons pluck pour extraire le premier élément du tuple.
Trouvez le nombre de user_id différents dans la table tw_followers_id, en utilisant les fonctions cytoolz.
Les fonctions qui pourront vous êtres utiles ici :
ct.count(seq)
=> compte le nombre d'éléments d'une séquencect.unique(seq)
=> à partir d'une séquence, renvoit une séquence où tous les doublons ont été supprimésVous vous rappelez sans doute que nous utilisions systématiquement pluck et map pour les exemples du cours, ceux-ci ne sont pas nécessaires, ici.
import cytoolz as ct
import cytoolz.curried as ctc
ct.count( ct.unique( cursor_sqlite.execute( "SELECT user_id FROM tw_followers_id") ) )
73
A l'aide de ct.compose
, créez une fonction comptez_unique qui effectue directement cette opération. Pour rappel, ct.compose( f, g, h, ...)
renvoit une fonction qui appelée sur x
exécute (f(g(h(x))
). ct.compose
prend un nombre d'arguments quelconque. A noter que les fonctions données en argument doivent ne prendre qu'un seul argument, ce qui est le cas ici. Pensez bien que comme vous manipulez ici les fonctions elle-même, il ne faut pas mettre de parenthèses après
import cytoolz as ct
import cytoolz.curried as ctc
comptez_unique = ct.compose( ct.count, ct.unique )
## Pour tester votre code, cette ligne doit renvoyer le même nombre qu'à la question 2
comptez_unique( cursor_sqlite.execute( "SELECT user_id FROM tw_followers_id") )
73
Nous allons utiliser la fonction comptez_unique définie précédemment pour comptez le nombre de "location" différentes dans la table tw_users.
Pour cela il faudra faire appel à deux fonctions :
json.loads
pour transformer une chaîne de caractère au format json en objet python).Il faudra sans doute appliquer ct.pluck
deux fois, une fois pour extraire la colonne content du résultat de la requête (même si celle-ci ne comprend qu'une colonne) et une fois pour extraire le champ "location" du json. Les syntaxes de ces fonctions sont les suivantes :
f
, ici vous faites références à la fonction, pas son résultat)Astuce : dans le cas improbable où vous auriez un ordinateur sensiblement plus lent que le rédacteur du tp, rajoutez LIMIT 10000
à la fin des requêtes
import cytoolz as ct
cursor_sqlite.execute( "SELECT content FROM tw_users")
comptez_unique( ct.pluck("location", ct.map(json.loads, ct.pluck(0, cursor_sqlite))))
# Le résultat attendu est 13730
13730
Comme on risque de beaucoup utiliser les fonctions ct.map
et ct.pluck
, on veut se simplifier la vie en utilisant la notation suivante :
pluck_loc = ctc.pluck("location")
map_loads = ctc.map(json.loads)
pluck_0 = ctc.pluck(0)
Notez bien que nous utilisons ctc.pluck et non pas ct.pluck, car le package cytoolz.curry (ici importé en temps que ctc
) contient les versions de ces fonctions qui supportent l'évaluation partielle. Les objets pluck_loc, map_loads, pluck_0 sont donc des fonctions à un argument, construites à partir de fonctions à deux arguments. Utilisez ces 3 fonctions pour simplifier l'écriture de la question 4
import cytoolz as ct
cursor_sqlite.execute( "SELECT content FROM tw_users")
comptez_unique( pluck_loc( map_loads( pluck_0(cursor_sqlite))))
# Le résultat attendu est 13730
13730
A partir des fonctions précédentes et de la fonction compose, créez une fonction get_json_seq, qui à partir d'un curseur d'une requête dont la colonne content est en première position, renvoit une séquence des objets json loadés. Vous devez pouvoir l'utiliser pour réécrire le code de la question précédente ainsi :
import cytoolz as ct
cursor_sqlite.execute( "SELECT content FROM tw_users")
get_json_seq = ct.compose( map_loads, pluck_0 )
comptez_unique( pluck_loc( get_json_seq(cursor_sqlite)))
13730
On peut vérifier si une localisation contient le mot "Paris", avec toutes ces variations de casse possible avec la fonction suivante :
def contains_paris(loc):
return "paris" in loc.lower()
En utilisant cette fonction et la fonction ct.filter, trouvez :
ct.filter
s'utilise avec la syntaxe ct.filter( f, seq )
et renvoit une séquence de tous les éléments de la séquence en entrée pour lesquels f renvoit true
. Vous aurez besoin des fonctions ct.unique et ct.count. Si vous avez une sortie du type <cytoolz.itertoolz._unique_identity at 0x7f3e7f3d6d30>
, rajouter la fonction list( ... )
autour pour forcer l'évaluation.
## Réponse 7.1
import cytoolz as ct
cursor_sqlite.execute( "SELECT content FROM tw_users")
ct.count( ct.filter( contains_paris, pluck_loc( get_json_seq(cursor_sqlite))))
## le résultat attendu est 5470
5470
## Réponse 7.2
import cytoolz as ct
cursor_sqlite.execute( "SELECT content FROM tw_users")
list(ct.unique( ct.filter( contains_paris, pluck_loc( get_json_seq(cursor_sqlite)))))
## la liste doit contenir 977 éléments
['Paris, Ile-de-France', 'Paris', 'Rouen/Paris, France', 'paris 75006', 'paris 75018', 'Somewhere in Paris area', 'Paris.', 'Paris, New York, Indonesia', 'PARiS', 'Now in Paris', 'paris', 'Paris France', 'Est parisien', 'PARIS', 'Paris ', 'paris ', '- Paris -', 'France, Paris', '10, rue du Colisée 75008 Paris', 'Paris - Munich', "Paris' Beach", 'Paris - Uppsala', 'Groupe La Poste -Paris', 'France (Paris) ', 'Paris, France', 'Normandie Deauville et Paris', 'Paris /France', 'Perpignan, Paris, Cordoba (SP)', 'Paris - Arras - Gascogne ', 'Paris / Toulouse / Krakow', '#Paris, Ile-de-France', 'Montréal - Paris', 'Limoges/Paris', 'LyonParisClermontNantesMorzine', 'paris 13', 'Grenoble, Paris, Annonay ', 'Bordeaux/Paris', '#Paris5', "Paris / Ile d'Yeu", '#Paris - #Angers', 'Paris et Toulouse', 'Paris 19ème', 'paris Marais', 'REIMS. METZ. PARIS', 'Europe Paris Nantes', 'Auxerre Paris Toulouse', 'Paris, 3e', 'Paris - Montpellier', 'Paris-Reims', 'Paris - Prague', 'Paris, Montréal, NYC', 'Paris - Lyon - Grenoble', 'villeparisis', 'Paris 75019', 'Nîmes | Paris', 'Paris, FRANCE', 'Strasbourg, Paris', 'Paris, France.', 'Paris / NYC / London', '16 Rue Amélie 75007 Paris', 'Paris. France.', 'Paris - Saint Malo', 'Paris 9e', 'Paris - France', 'Rennes/Paris France', 'Perpignan - Paris', 'Compiègne - Paris - FRANCE', 'Montbeliard Paris ça depend !', 'Paris . Londres . Shanghai .', 'Paris - La Rochelle - Corrèze', 'PARIS ', 'Paris/Brussels/Montargis', 'Paris...et ailleurs ', 'Paris / Madrid / Hanoi / Yango', 'Paris, London, the world', 'France, Lyon, Guadeloupe,Paris', ' Paris-Martinique ', 'Paris / Pau / New-York', '#Paris', 'Paris-Gaiole in Chianti-Senlis', 'Strasbourg, Lyon, Paris', 'Paris - Bruxelles - Genève', 'near Paris', 'France . Paris .', 'Rennes, Paris', 'Région parisienne', 'bruxelles/paris/capferret', 'Romainville (Greater Paris)', 'Paris - Bordeaux', "L'amour de la cuisine & Paris ", 'Entre Paris et Val-de-Marne', 'Le Touquet. Lille. Paris', '#Paris #Paris10', '75002 Paris, france', 'Paris/Blagnac', '77 Rue de Varenne Paris, France', '75005 Paris', 'Paris France', 'Paris Marais', 'Paris, Dijon', '18, rue de Tilsitt - Paris 17 ', 'France(Bordeaux,Paris,Cognac)', 'Paris France ', 'Paris, Lycée Henri IV', 'France,Paris', ' paris rennes', 'Paris, Tours, Vendôme', 'Paris - Aveyron', 'Paris - Berlin - Shanghai', ' Paris 9', 'paris,france', ' Paris et Nice', 'Paris, Biarritz', 'Paris - Quebec - Atlanta', 'Paris-Bruxelles', '15ème arrondissement de Paris', 'Lyon - Paris', 'nantes/paris', 'Paris (France)', 'Personal tweets from Paris, FR', 'Paris, france', 'Bourguignon expatrié à Paris', 'Paris et ailleurs', 'Paris / France', 'Tours-Blois-Orleans-Paris', 'Bamako / Paris ', 'paris 7e', 'Paris, Lyon, Europe...', 'Paris (almost)', '#93270 #GrandParis #Bruxelles ', 'Paris - Montrouge', 'DAKAR Paris Joburg Montreal', 'Paris, London, New York', 'Paris-France', 'Paris, 75004', 'Paris 75017', 'Paris Aix Portigliolo', 'Lille - Paris // France', 'Goussainville - Paris - Nice', 'Paris - Lille - Seoul', 'Ardèche - Paris', 'Paris, Ile-de-France, France', 'Babi, Barca, 75% loin de Paris', 'paris, France', 'Often in my sneakers (Paris)', 'Paris / Nîmes', 'Planet e@rth Zoom @Paris', 'Headquarter Paris, France', 'Rochemaure / Paris', 'Paris – Niort', 'Paris / Berlin / Hamburg', 'Paris/StNicolasdAcy/CapNegre', 'paris\n', 'Paris 4', 'Madrid y Paris. No sólo.', 'Paris - Toulouse', 'Paris - Nanterre - Versailles', 'Paris/Toulouse', 'Lille & Paris', 'Paris 11e ', 'Paris & London', 'UGC ciné cité Paris 19eme', 'Paris 15ème', 'London (no longer Paris)', 'Paris - Berlin', 'Paris / TLV / Guayaquil / Nice', 'Paris/Biarritz', 'Paris,France', 'New york ,London,Paris,Monaco', 'Paris-New York', 'Paris IIX', 'Paris - Bruxelles', 'Paris 4e', 'Banlieue Parisienne', 'Exilé béarnais à Paris - ', 'Paris 2ème', 'Lyon Paris Bordeaux Nantes', 'Paris, Bilbao, Dieppe', 'paris idf ', 'ParisMLP2017 Natio Remigration', 'Toulon//Lyon//Paris//Nantes', 'Paris France World...', 'France Paris', 'paris, france', 'Paris - France, Europe. ', 'Dakar-Paris', 'Paris, Lyon, Barcelone', 'Toulousain en escale à PARIS', 'Paris / Miami', 'Paris, France ', 'Paris - Strasbourg - Bruxelles', '94170 nojent sur marne paris', 'Paris - Marseille', 'Argentan - Paris - Caen', 'Paris / Genève ', 'Paris 14', 'Rabat-Paris', 'München - Paris', 'Ayoub 20 Paris # La Vida Lokaa', 'Capetown / Paris', 'Nantes/Paris/Strasbourg/Annaba', 'Kinshasa Paris ', 'paris 20e', 'Region de Paris FRANCE', 'Paris Nantes ', 'Paris, Fr', 'Paris/Lyon, France', 'paris panam', 'Paris/Lectoure (32)', 'Carpentras - Paris', 'Paris, Montmartre', 'France, Paris, 18eme', 'Parissss', 'Paris. Lormes.', 'Paris 12e', 'Rennes - Paris ', 'Paris - Lille', 'Paris/Tunis', 'Paris, 18e arrondissement', 'Toulouse/Linköping/Paris', 'Paris/France', 'Paris 13 & Glumotte, Groland ', 'Paris, Beauvais, Bretigny', 'Paris et sa banlieue ', 'Paris XIV', 'Paris, La Roche sur Yon', 'Paris : France', 'Paris 18 / Matane', 'Paris - Versailles - Angers', 'Paris, London, Barcelona', 'ASNIERES, PARIS', 'France,Région parisienne', "Goutte d'or #paris18", 'Paris et Lyon...', 'Paris, FR', '75017 Paris', 'Avignon-Montpellier-Paris ', 'auvergne et paris ', 'Paris/Bastia', "Paris' cambrousse", 'paris 15', 'Lille - Le Touquet - Paris 8', 'PARIS FRANCE', 'Paris 3e', 'Paris (West Side)', 'paris france', 'LONDON - PARIS', 'FRANCE Paris', '#Paris, #Hamburg', 'Paris - Montréal', 'Paris, 10 ème', 'Metz, Paris, Montréal', 'Paris (France)Bhaktapur(Nepal)', 'Paris / Toulon', 'PARIS IDF FRANCE', '#UnivParis8 salle ME-23 MDE', 'Paris Montpellier', 'Paris, Laval, Singapore.....', 'Montpellier & Paris', 'paris / beyrouth / erevan', 'Paris 75020', 'Paris - Saint-Cloud', 'Paris ~ Somewhere', 'Lille, Paris', 'Paris/Saint Germain en Laye', 'Rue du bac, Paris VII', 'Avignon/Paris', 'PARIS 19E ARRONDISSEMENT', 'Paris/ Tours / Strasbourg', 'Aquitaine, France,paris,mali,', 'Washington DC - Paris', 'Paris-IDF', 'Paris, Toulouse, Pamiers', 'Paris et ses alentours', '15 rue de la Bruyère - 75009 Paris', 'Санкт-Петербург & Paris 6th', 'Paris, 8ème', 'Paris/Nice', 'Honfleur / Paris / Antibes ', 'Paris, Cannes', 'PARIS / NICE', 'Ile-de-France, Paris', 'France paris', 'Île-de-France, Paris', '#Paris 14', 'Paris 12', 'PARIS 75012, PARIS 75017', 'paris 20ème', 'Paris - france', 'Paris/Banlieue Ouest', 'Paris / İstanbul', 'Lille - Paris - Londres', 'Paris-La Mecque باريس-مكة', 'Paris, France / Dakar, Sénégal', 'Moscow, Paris ', 'Paris - FRANCE', 'France , paris', 'Auvergne - Bruxelles - Paris', 'Bordeaux, Paris, Lausanne', 'Sciences Po Paris', 'Lille/Paris à mi temps', 'Grenoble - Lyon - Paris ', 'Paris - Séoul', 'Rouen - Paris - Marrakech', 'Paris - Boulogne-Billancourt', 'Casablanca/Paris/NYC', 'Casa-Paris ', 'Paris - Nagoya', 'Paris - London', 'Bastia / Paris', 'Paris - Пари́ж', 'Rabat/Morocco, Paris/France', 'PARIS 75018', 'PARIS, France', 'Paris - Montluçon', 'PARIS, FRANCE', '97 Rue du Bac, 75007 Paris', 'Paris, Hong Kong', 'Cité des Sciences - Paris', 'Paris,France ', 'Paris, France. Tweets FR/EN/ES', 'Paris ✩ La', 'Stockholm/Paris', 'Paris 75016, France', 'paris lille bruxelles varsovie', 'Paris/Rabat', 'Paris Bordeaux Amiens', 'paris14', 'Paris / Grenoble', 'Paris New York HK', 'paris xx', 'entre Paris et la Bretagne ', 'Boulogne / mer- Lille - Paris', 'PARIS et le coeur à DJIBOUTI', 'paris 17', 'Diocèse de Paris', 'Polska. Gwada. Paris. London', 'Paris 10e', 'Alger Paris', 'Paris - Lyon - Francfort', 'Rouen et paris', 'Paris / San Francisco', 'Paris- Singapore', 'paris v', 'Paris 2', 'Paris - Lyon - Marseille', 'Paris Cargese ', 'Paris-Lisbonne', 'Paris-Tunis', 'Paris 11ème', 'paris, 19', 'Paris et Montreal', 'Nantes Paris Roscoff ', 'paris18eme', '#Paris9', 'France - Paris', 'Paris, Orléans', 'Paris, Ile-de-France_ Algerie', '#Paris5 France', 'paris-rive gauche', 'For now, in Paris', 'Paris/Lomé/Abidjan ', 'Alger-Paris', 'FR - Paris/Lille', 'Paris - Hamburg', 'Paris ; Lyon', 'Vannes - Rennes - Paris', 'Paris 20', 'Rue Lamarck, Paris 18', 'Rgion Parisienne', 'REGION PARISIENNE', 'Paris XV', 'Glasgow/Paris', 'Saint-Ouen, Paris, France', 'Paris - Toulouse - France', 'Paris, Nantes', 'Paris, Tours', 'Paris (20ème arrondissement)', 'Paris - Algeciras', 'Paris 15ème arrondissement', 'Paris - Issy ', 'Paris Bordj Bou Arreridj', 'paris 92', 'Paris 10ème', 'Paris + Shanghai + Italy', 'Paris - Normandie', 'Paris 9', 'Paris, Telecom ParisTech', 'Paris - Worldwilde', 'Paris - Buenos Aires', 'paris/telaviv', 'Ferrandi Paris (75006)', 'Brussels - London - Paris', 'Paris 17ème', 'Paris - Strasbourg', 'paris 18ème', 'Bondy_Paris-France', 'paris 75013', 'Paris - Caen', 'Bruxelles -- Paris ', 'Marseille - Paris', 'Paris - Stockholm', 'paris75019', 'paris 11', 'Stbg / Paris', 'Paris - Montmatre ', 'Paris-Sabadell', 'Coeur à Nantes - Corps à Paris', '75015 Paris ', 'Paris-Dakar', 'Cuxhaven (D), Paris (F)', 'Paris & Ajaccio', 'Paris XX', 'Paris - Beauvais', 'Paris, Chicago, Madrid, Moscow', 'Berges de Seine - Paris', 'são paulo / paris', 'Brussels & Paris', 'Paris - Córdoba', 'France -Paris', 'Paris / London', 'Paris/Europe', 'trieste-sardinia-paris', 'Paris, Texas', 'Paris - Val-de-Marne', 'Paris, 13ème arrondissement', 'Longjumeau bientôt Paris', "45 rue de l'esperance paris", 'São Paulo, Paris', '86, Bd de Charonne, Paris', 'Paris4 / Pantin', 'Paris 14ème', 'Paris, France - via SF', 'Paris (20e ardt)', 'PARIS10', '14 rue du Cygne, 75001 PARIS', 'Paris XII', 'Paris intra-muros', 'Entre Rennes & Paris', 'tiaparis', 'Paris-Goudelin-Abidjan-Ouaga', 'Paris, Ile-de-France XX', 'PARIS..with LOVE', 'Paris (enfin pas très loin)', 'Mentally in Paris & Copacabana', 'Rabat | Paris | 18 ', 'NYC, London, Paris, Lomé', '7 rue Lincoln - 75008 Paris', 'Kyprianos-Athens-Paris', '| PaRiS| FrAnCe ', 'Paris , Nantes', 'Paris, Grand Ouest', 'Paris-Lagnieu', 'Paris/IDF_Issy les moulineaux', 'Shanghai, Tokyo, Paris', 'france , paris ', 'Paris/Brussels', 'France - Paris', 'Brasil - SP-Paris', 'Paris, France. ', 'Paris, France. 13ème.', 'Paris/Dhaka', 'Paris ou Bamako, Mali', 'Metz, Nancy, Paris', 'Paris, FR.', 'Paris, à deux pas du 77', 'marseille et paris ', 'PAris ', 'Paris ile de france', 'Deauville/Paris ', 'Paris VIII -Miami', 'Ile de France, Paris', 'Paris/Caen', 'Paris Washington', 'Bordeaux. Paris. Cassis', 'Paris, Malmö (Suéde) (Sweden) ', 'Paris - Rennes - Saint-Brieuc', 'Paris, Europe', 'Paris / Lille / Bonus SF ', 'Paris-Montreuil-Bordeaux', 'Paris, Mexico', 'now in Paris', 'Montreal | Paris', 'Angers - Paris ', "14 avenue d'Eylau 75116 Paris", 'Paris, Londres', 'France, Bordeaux-Paris', 'Paris - Madrid', 'Paris - Grenoble', 'Région parisienne, France', 'paris / france', 'Brussels, Paris, Philadelphia', 'Paris,Sud ♥ =~}', 'Paris, la Capitale', 'Paris City', 'Paris / Palma de Mallorca ', 'Paris, Toulouse', 'Paris of the Plains', 'Paris Essonne', 'Paris,FRANCE', 'Paris (75002)', 'Paris 15', 'France/Paris', 'Paris - Fort de France', 'Paris, 3éme', 'Paris/London/Nottingham', 'HEC Paris', 'Fontenay en parisis', 'Paris. Enfin, à côté quoi.', 'Région Parisienne', 'Paris & Denver', 'Bastia - Paris', 'Paris 75014', 'Madrid , Paris ', 'Paris(L)', 'Guadeloupe-Geneva CH- Paris FR', 'Paris / IDF', 'Paris,Milan ', 'New York & Paris', 'PARIS 16EME', 'ADIAKE -ABIDJAN - PARIS', 'Nice, Paris, Amsterdam', 'Paris 20ème', 'Nice | Lyon | Paris ', 'paris/lyon', 'Calgary / Paris ', '2 rue de Viarmes, Paris', 'Paris et un peu ailleurs', 'PARIS / TANGER', 'Paris Tehran The World', 'Maastricht, Paris and South FR', 'Paris, Montreal, Palo Alto', 'Paris / Montpellier', 'Paris 18e', 'Bordeaux et Paris', "11, Rue de L'etoile, Paris", 'Bordeaux-Paris-Provence...', 'Paris - Saint Tropez', 'Deauville - Paris ', 'Paris-Reutlingen-Munich', 'Paris-BZH', 'Je ne suis pas parisienne. ', 'Between Paris & London', 'Paris / Madrid', 'Paris 17', 'Paris - Rueil Malmaison', 'PARIS, FRANCE.', 'Paris Lima', 'Paris, Lyon ou Lisboa', 'Paris/Hanoi', '2 Avenue Gabriel, 75008 Paris', 'São Paulo / Paris', 'Paris, London, Wonderland.', 'Macinaggio / Paris', "ICI C'EST PARIS !! Psg3", 'Paris 17eme', 'Paris, France (obviously)', 'Paris/LA/ Jamaica ', 'France, Paris, Marne la Vallée', 'Genève, Paris', 'Paris - Île de France', 'paris 75003', '24 R. Pépinière 75008 PARIS', 'franch,paris', 'Paris - France ', '- Paris - ', 'Paris /// New York', 'paris london', 'Paris │ New York', 'Paris - Brest', 'Paris, Nice, Brest', 'PARIS-LONDON-ROMA', 'Brest - Paris', 'La Baule, Nantes, Paris', 'Paris aera', 'Paris - Evry ', 'Massy, Région parisienne', 'Paris et souvent ailleurs. ', 'Paris area, France', 'Paris Area', 'Paris , France ', 'oui je suis a paris', 'Montréal Paris', 'Nouvelle Athènes, Paris', 'PARIS BARCELONE MARRAKECH', '3 rue pache 75011 paris ', 'Marseille, Montpellier, Paris ', 'Paris & New-York', 'Paris, Roubaix, Bordeaux', '55, rue des Archives Paris 3', 'Juan les Pins/Paris', 'Paris - France-Allemagne', 'De Paris à la Méditerranée !', 'Paris-Le Mans-Tréflez', 'Paris - behind a cloud', 'Paris/Lyon/Marseille/Lille', 'Paris - TLV', '75015 Paris', 'Casablanca - Paris - #Afrique', 'Berlin & Paris', 'paris 14eme', 'Paris - Ile-de-France', 'Douala-Yaoundé - Paris - Dubai', 'Paris - Chantilly', 'Edinburgh-Taipei-Phoenix-Paris', 'Paris Casa', 'Lyon et Paris', 'Paris-France..jijel 18', 'PARIS 75', 'paris VI', 'Paris FRANCE', 'Paris - Londres', 'Paris, Casablanca', 'Bangalore / Paris', 'Paris, rive gauche', 'paris, France ', 'Paris/Bordeaux ', 'Paris, France / Ниш, Србија', 'San Francisco et Paris', 'London, Paris', 'PARIS 8', 'Arras - Paris', 'Bologna • Paris • Milano', 'Bruxelles | Paris | Berlin', 'pAris, France', 'Paris Tel Aviv', 'Parisian - Caribbean ', 'PAris', 'Paris / SF', 'Le Mans. Paris', 'nvlle parisienne/hbte du monde', 'Close to Paris', '#OserParis #Paris7 #Paris2014', 'Montpellier, Paris', 'France Paris // Corsica Bonif', 'Paris 18', '#Paris ', 'Paris, avenue Velasquez', 'PARIS - LYON - LE MONDE', 'Oslo/Paris', 'Paris & Shanghai', 'Paris 19', 'Saint-Rémy de Provence, Paris', '75020 PARIS FRANCE', 'Paris_France', 'Niçoise à Paris', 'Paris, Montpellier & Partout.', 'Palais des congrès, Paris', 'Paris - Biarritz - Twitter ', 'PARIS 01', 'pres de paris', 'Lives in Paris & Bannockburn.', '58 rue Tiquetonne - PARIS', 'paris17', 'Dijon-Paris', 'Paris-Strasbourg', 'De Bruxelles à Paris', 'France région parisienne', 'Paris 75 020', 'Paris/London', 'Quimper / Paris', 'Paris 75011', 'Paris/Rodez', 'Paris / Strasbourg ', 'PARIS / m2c2 biotechnologie', 'Corps à Paris, cœur à Lyon', 'Paris (ou pas)', '@Alger @Paris ', 'Hong-Kong/Paris', 'Paris-Paimpol', 'Paris / Evreux / Le Havre', '20, rue du Temple, Paris IV', 'Paris, for a start', 'france paris ', 'Paris/Montpellier', 'Paris - ToLose', '#GoutteDOr, Paris', 'Paris 7', 'Paris - Moselle', 'Paris/ Nantes/Rennes/Bs Aires', 'Marseille / Paris', 'France, Bordeaux, Paris', 'Martigues (13) et Paris (75)', 'Paris. France', 'Москва-Paris', 'Corse, Paris', 'Paris 6ème/5ème ', 'Paris - Strasbourg - Mende', 'Paris 14ème et Nice', 'Paris Est', 'Paris, Île-de-France', 'Paris - Bruxelles ', '32 RUE DE CONSTANTINOPLE PARIS', 'Paris, Sud de Seine (92)', 'Many. Heart in Paris!', 'Paris&L.A', 'Lyon & Paris', 'Paris ou avec @tamerelapute', 'London + Paris/Rabat/Eurostar', 'Paris / Genève', 'PARIS FRANCE', 'Paris, London, Europe ', 'Paris, Grenoble, Valence', '#Paris #Madrid #Aix', 'Paris/Bruxelles', 'Paris 75003', 'Paris - Lyon', 'paris.compte officiel twiter ', 'NCY - PARIS - NYC', 'unkut/paris', 'Paris / Tunis', 'Nantes (Paris)', 'paris/LA', 'Martinique-st Martin-Paris', '∞ Paris ∞', 'Paris - Bordeaux - New York', 'Valence - Le Touquet - Paris ', 'Paris - Versailles, France', 'Paris, st germain ', 'paris dijon', 'Berlin, Paris', 'Paris // Reims', 'Paris IV', 'Paris •', 'Paris, Brussels, Miami', 'Paris, Sarajevo...', 'Paris- France', 'Paris / NYC', 'London - Paris - Morocco', 'Luxembourg - Paris', 'paris ,france', 'paris/france', 'Martinique →Paris ', 'Paris/Geneva/Casablanca', 'Paris. ', 'Paris !!!', 'Paris 10', 'Valencia/Paris', 'Paris & Embrun(05)', ' Paris - Madrid - Avignon', 'paris-sorbonne paris IV', 'Paris, London and beyond !', 'Paris / Cannes ', 'Paris 1er ; Paris 19e', 'Paris Ici & Ailleurs', 'paris 10', 'Paris - Alger - Dakar', 'paris 11eme', 'Paris 13e', 'Texas - Paris', 'France- Paris-Toulouse', 'Paris based | NYC | SF', 'paris(france)', 'Pariss', 'paris 10eme et 17eme ', 'Metz-Paris-World', 'Paris, fr', 'Berlin / Paris', 'Paris-Havana', 'London / Paris / Chicago / NY', 'Paris (14ème)', 'paris.london', 'Paris, Los Angeles', 'champigny-sur-marne ,paris', 'Paris, Auvergne, Perigord', 'paris 15 ', 'Paris XI', 'Paris Xème', 'Maison des secret,Paris', 'Paris 75003, France', 'Paris | Monaco | Bordeaux', 'Paris et Nantes', 'Boston / Paris', 'Le Mans / Paris', 'Tours-Paris-La Roche sur Yon', 'Caen, Rouen, Paris', 'Tokyo-Paris-Nice', 'Paris / Martinique', 'Lille, Sophia, Paris', 'Los Angeles - Paris', 'Photographer: Paris-Normandy', '24 Rue Hénard 75012 Paris ', 'Paris/orleans/chateauroux/', 'Paris - Sydney', 'France Paris ', '9 Rue Littré, 75006 Paris', 'Nantes - Paris', 'Chicago/Madrid/Paris', 'Tel: 01 48 74 22 22 - Paris', 'Lille - Paris - Bruxelles', 'Paris - Haute-Savoie.', 'Paris based, global reach', '51, rue des Archives, Paris', 'Paris, Chantilly - FRANCE', '22, rue Huyghens Paris 14e', '104, bd Arago 75014 Paris', '17 rue Dupin 75006 Paris', '#Paris #Tours #Bordeaux #Mars', 'Paris 16', '#Nantes - #Paris', 'Paris Lyon', 'Paris XVe', 'Paris, Ile-de-France 75015', 'Paris & Belgium', 'Paris/Versailles', 'Paris, Bordeaux', 'PARIS FR', 'Nancy, Paris', 'Paris - Genève - Hong Kong', 'Paris Butte aux Cailles', 'Paris/Lordran', 'paris (france)', 'Paris & Bonn ', 'Vichy, Paris, Bucarest', 'Rouen - Paris', 'Paris | Aurillac', 'Paris - Toulouse ', 'Clermont-Ferrand vs Paris', 'BORDEAUX/PARIS', 'Avenue Ledru-Rollin, Paris', 'Paris, XVIIè', 'Paris - La Rochelle', '23 Rue des Bernardins PARIS 5°', 'Paris - Tunis - Alger', 'paris le bourget', ' Paris Barcelone Cap Salou ', '#Paris #Alençon ', 'Paris -France', 'London, Paris, Dubai.', 'Paris, Nantes, Gap - France', 'Paris // FRANCE', 'usually in Paris', 'Paris / Bordeaux ', 'PARIS 75005', ' Paris', 'Villeneuve, Paris', 'Paris / Antony', 'paris/92100 - Moscow', 'Troyes / Paris, France', 'Paris (92)', 'Next to Paris - France', 'FRANCE / PARIS', '29 Quai Voltaire Paris 7e', 'France, Paris,Bretagne,Mayenne', 'Paris-Albertville', 'Paris - Espace Pierre Cardin', 'Paris XV3', 'Paris | Formentera | Ibiza', 'Paris, France; Taipei, Taiwan', "Paris & bassin d'Arcachon ", 'Paris 02, Île-de-France', 'France - Paris - Lille', 'Paris IIXème', 'Paris - Montpellier - Belfort', 'World: Europe: France: Paris.', 'Paris • Shanghai', 'paris (gerardmer)', ' Paris - Cannes ☀️', 'Paris La Défense', 'paris montmartre', 'Paris — L.A. (Les Abbesses)', 'Paris & Normandie', 'Paris, kinshasa ', 'Troyes / Paris ', 'PARIS 75 ', 'Usually, Paris, France.', 'Paris come From West Indies', 'PariS', 'Paris-Tehran', 'Paris & Clermont-Ferrand', 'Paris 11', 'Paris, Ile-de-France FR', 'poissy, paris, france', 'Paris / Orléans', 'tehran and Paris ', 'Paris-Brussels-Geneva', 'Nantes - Rennes - Paris - Lyon', '#BZH #Paris ', 'Sunnyvale, CA + Paris, France', 'Lannion / Avignon / Paris', 'Bordeaux / Paris / Los Angeles', 'morntérial-paris-casa-doubai', 'pARIS', 'Paris - Île-de-France - France', 'Paris (FRANCE)', 'Rio / Paris ', 'paris 75009', 'paris. france', 'Cali, Edinburgh, London, Paris', 'Paris VIII', 'Paris, Concarneau, Marseille', '103 bvd Saint Michel Paris', 'Paris, Lyon', 'Bordeaux - Paris - Marrakech', 'Lyon / Paris', 'NYC-Paris-Milano', 'London / Paris', 'Modena ✈️ Paris', 'Paris - 75017', 'PARIS & CHARTRES', 'Paris | Québec', 'Amiens - Paris - Tours', 'Nantes, Nîmes, Paris, ...', 'Paris, Beijing, London', 'Paris / FRANCE', 'Paris, Ile de France', 'Paris - Montpellier - Houston', '50 rue Richer • Paris 09', "ici c'est Paris", 'London/Paris', 'Paris, New York, Dubai', 'Paris - Rio - SP', 'New York, London & Paris', 'PARIS - NEW YORK - HONG KONG', 'Paris - Inde - Etats-Unis', 'Paris/NYC/frenchriviera', 'Paris - Site Encres de Chine:', '2 Rue Vivienne 75002 Paris', 'Besançon - Paris', 'Paris Marne-la-Vallée', 'Martinique-Paris-Lyon-Madrid', 'Toulouse, Andernos, Paris', 'bruxelles (belgique) , Paris ', 'Paris Sorbonne', 'Paris/Levallois', 'Paris, UK', 'Paris - Mallorca', 'Paris | Réunion', 'Paris - Los Angeles ', '26 rue de Berri 75008 Paris', 'Boston Milano Paris London', 'Paris - Shanghai', 'Paris - Lorraine', 'caen paris', 'Bastia, Marseille, Paris...', ' Paris', 'Marseille / Paris ', 'Paris , France', 'Paris// Issy les moulineaux', 'Paris / Liverpool / Propriano', 'Paris/Lyon/Cannes/Hawaii', 'Paris - Lisbonne ', 'Strasbourg / Paris', 'Paris, Lausanne', 'Lyon - Lille - Annecy - Paris', '#Paris11', 'Toulon/Paris ', 'Paris et Seine-Saint-Denis ', 'Paris 13', 'Paris/New York', 'PARIS - TUNIS ', 'Paris&Provence', 'Paris/Antibes/Cannes', 'Paris/Versailles/Le Chesnay', 'Paris, Porte de Versailles', 'Paris, 18ème', 'Paris XVème', '#Nantes #Paris #Reims', 'paris8', 'Paris-Marseille-Glénan, France', '#Paris #NewYork', 'Paris / Los Angeles', 'Paris\n', 'Nantes - Paris - Dinard', 'Paris 75', 'LILLE/PARIS', 'paris francia', 'Paris - Köln', 'France, Paris.', 'Pau/Paris', 'Saint-Quentin / Paris', 'Villeneuve, Paris, monde', 'Paris et Grand Quevilly', 'Paris Le Mans Sablé', 'Paris – Montreuil', 'Paris/Amsterdam', 'Lyon et Paris, France']
Calculez le nombre de tweets total par les utilisateurs dont la "location" contient Paris.
Dans le json de twitter, la clé pour cela est "statuses_count"
Pour cela plusieurs possibilités :
Réponse attendue : 9811612
## Réponse 8
import cytoolz as ct
# solution 1
def contains_paris_json(loc):
return "paris" in loc["location"].lower()
cursor_sqlite.execute( "SELECT content FROM tw_users")
# Ici j'utilise pipe, car les fonctions sont appelées dans l'ordre indiqué, cela est plus pratique
# que compose
print( ct.pipe( cursor_sqlite,
get_json_seq,
ctc.filter(contains_paris_json),
ctc.pluck("statuses_count"),
sum ) )
# Solution 2 - déconseillée
cursor_sqlite.execute( "SELECT content FROM tw_users")
# Ici j'utilise pipe, car les fonctions sont appelées dans l'ordre indiqué, cela est plus pratique
# que compose
print(
ct.pipe(cursor_sqlite,
## on récupère les objets json
get_json_seq,
## on regroupe par localisation
ctc.groupby("location"),
## on ne garde que les entrées dont la clé contient paris
ctc.keyfilter(contains_paris),
# Pour les valeurs, ont fait la somme des statuses_count
ctc.valmap( ctc.compose( sum, ctc.pluck("statuses_count") )),
lambda x:x.values(),
sum ))
# Solution 4 - valable également
cursor_sqlite.execute( "SELECT content FROM tw_users")
print(
ct.pipe(cursor_sqlite,
## on récupère les objets json
get_json_seq,
## on garde la location et les nombres de status
ctc.pluck(["location","statuses_count"]),
## on applique le filtre, avec contains_paris sur le premier élément
ctc.filter( ctc.compose( contains_paris, ctc.get(0)) ),
## une fois le filtre appliqué, on ne garde que les statuses_count
ctc.pluck(1),
# on fait la somme
sum))
9811612 9811612 9811612
On va maintenant s'intéresser à la proximité / corrélation entre les hommes politiques, que l'on mesurera à partir de la formule :
$\frac{1}{2}*( \frac{nbFollowersCommun}{nbFollowersHommePolitique_1} + \frac{nbFollowersCommun}{nbFollowersHommePolitique_2}$)
On prend donc la moyenne des ratios des followers de chaque homme politique suivant l'autre (cette formule semble s'accommoder assez bien des différences du nombre de followers entre homme politiques)
On s'intéressera notamment aux hommes politiques suivants :
benoithamon | 14389177
montebourg | 69255422
alainjuppe | 258345629
De fait vous pouvez prendre n'importe quel homme ou femme politique, les résultats de cette méthode sont assez probants malgré sa rusticité.
Important : pensez à appliquer la cellule ci-dessous
try:
cursor_sqlite.execute("CREATE UNIQUE INDEX tw_users_id_index ON tw_users(id)")
print("Index created")
except sqlite3.OperationalError as e:
if( "index tw_users_id_index already exists" in str(e)):
print("Ok, index already exists")
else:
raise e
Index created
La façon la plus simple est de charger les listes d'id de followers en mémoire, dans des objets de type set, et de les comparer avec les opérateurs & (intersection) - (différences).
On peut aussi chercher une méthode approchée, en comparant de façon aléatoire les listes contenues dans tw_follower_id.
part_taken = 2
get_all_followers_set = ctc.compose(
ctc.reduce(set.union),
ctc.map(set),
get_json_seq,
ctc.take_nth(part_taken),
ct.curry(cursor_sqlite.execute,"SELECT content FROM tw_followers_id WHERE user_id = ?"),
lambda x:(x,)
)
def proximite(a,b):
c = a & b
return min(1, 0.5*len(c)*(1/len(a)+1/len(b))*part_taken)
users_id_list = [ 14389177, 69255422, 258345629 ]
users_id_f_set = list( ct.map( get_all_followers_set, users_id_list ) )
for it_l in users_id_f_set:
print( ";".join("{0:.2f}".format(proximite(it,it_l)) for it in users_id_f_set) )
1.00;0.46;0.23 0.46;1.00;0.27 0.23;0.27;1.00
Essayez d'exécuter le code suivant
import dask
On affiche la liste des tables de la base sqlite:
from pyensae.sql import Database
dby = Database("twitter_for_network_100000.db")
dby.connect()
dby.get_table_list()
['tw_followers_id', 'tw_search_count', 'tw_status', 'tw_users', 'tw_users_history', 'tw_users_htmldata']
dby.close()
dask peut vous permettre de paralléliser de façon efficace votre code entre plusieurs processeurs. Utilisez le code suivant pour splitter la base 'twitter_for_network_full.db' en plusieurs fichiers plats (NB: pensez à nettoyer votre disque dur après ce tp).
import cytoolz as ct # import groupby, valmap, compose
import cytoolz.curried as ctc ## pipe, map, filter, get
import sqlite3
import pprint
try:
import ujson as json
except:
import json
conn_sqlite_f = sqlite3.connect("twitter_for_network_100000.db")
cursor_sqlite_f = conn_sqlite_f.cursor()
cursor_sqlite_f.execute("SELECT content FROM tw_users")
for it in range(100):
with open( "tw_users_split_{0:02d}.json".format(it), 'w') as f:
for it_index, it_json in enumerate( cursor_sqlite_f ):
f.write( it_json[0] )
f.write("\n")
if it_index == 100000:
break
else:
break
Calculez maintenant, en utilisant dask.bag :
## Code commun nécessaire
import dask.bag as dbag
try:
import ujson as json
except:
print("ujson unavailable")
import json
from operator import add
a = dbag.read_text('tw_users_split*.json')
# Le nombre total de status
a.map(json.loads).pluck("statuses_count").fold(add).compute()
80536977
# Le nombre moyen de tweet par location.
import cytoolz
# Solution
def mean(l):
# la parallélisation n'est pas effectuée de la même manière
# sur linux et Windows
# dans un process différent, les import
# faits au début du programme ne font pas partie
# du code parallélisé, c'est pourquoi il faut les ajouter
# dans le code de la fonction
import cytoolz.curried as ctc
return sum( ctc.pluck(1, l) ) / len(l)
def function_mapped(*args):
# Example of args:
# (('Lille', [['Lille', 483]]),)
if len(args) == 2:
x, y = args
else:
x, y = args[0]
me = mean(y)
return x, me
result = a.map(json.loads).pluck(["location","statuses_count"]) \
.groupby(0).map(function_mapped).compute()
result[:10]
[('www.ville-troyes.fr', 292.0), ('', 299.8513534747518), ('Pau/Paris', 2147.0), ('France', 2739.652611705475), ('Villeneuve, Paris, monde', 13678.0), ('Nice, France', 7690.0), ('Trappes - Yvelines', 3602.0), ('Paris', 5858.625), ('France - Bordeaux', 2134.0), ('Crest', 2240.0)]
# La distribution du nombre de followers par puissance de 10
import math
a.map(json.loads) \
.pluck("followers_count") \
.map(lambda x,math=math: int(math.log10(x+1)) ) \
.frequencies() \
.compute()
[(3, 1922), (1, 28931), (4, 290), (5, 65), (2, 13377), (0, 55480), (6, 5), (7, 1)]
Dans ce second cas, on a gardé la parallélisation mais il a fallu ajouter le module math au contexte de la fonction parallélisée.