Votre guide pour regrouper des données en Python – Real Python

By | novembre 18, 2019

Cours Python en ligne

Que vous veniez tout juste de commencer à travailler avec Pandas et que vous souhaitiez maîtriser l’un de ses principaux équipements, ou que vous cherchiez à combler certaines lacunes dans votre compréhension de .par groupe(), ce tutoriel vous aidera à décomposer et à visualiser un Pandas GroupBy opération du début à la fin.

Ce tutoriel est destiné à compléter la documentation officielle, dans laquelle vous verrez des exemples autonomes et concis. Ici, cependant, vous allez vous concentrer sur trois autres visites détaillées qui utilisent des ensembles de données du monde réel.

Dans ce tutoriel, vous allez couvrir:

  • Comment utiliser les opérations Pandas GroupBy sur des données réelles
  • Comment le split-apply-combine chaîne d'opérations fonctionne
  • Comment décomposer la chaîne diviser-appliquer-combiner en étapes
  • Comment les méthodes d'un objet Pandas GroupBy peuvent être placées dans différentes catégories en fonction de leur intention et de leur résultat

Ce didacticiel suppose que vous avez une expérience des Pandas eux-mêmes, y compris comment lire des fichiers CSV en mémoire en tant qu’objets Pandas avec read_csv (). Si vous avez besoin d'un rappel, consultez Reading CSVs With Pandas.

Vous pouvez télécharger le code source de tous les exemples de ce tutoriel en cliquant sur le lien ci-dessous:

Ménagère

Tout le code de ce didacticiel a été généré dans un shell CPython 3.7.2 utilisant Pandas 0.25.0. Avant de poursuivre, assurez-vous de disposer de la dernière version de Pandas dans un nouvel environnement virtuel:

$ python -m venv pandas-gb-tut
$ la source ./pandas-gb-tut/bin/activate
$ python -m pip installer des pandas

Les exemples ici utilisent également quelques options de pandas modifiées pour une sortie plus conviviale:

importation pandas comme pd

# Utilisez 3 décimales dans l'affichage en sortie
pd.set_option("display.precision", 3)

# Ne pas encapsuler repr (DataFrame) sur des lignes supplémentaires
pd.set_option("display.expand_frame_repr", Faux)

# Définir le nombre maximal de lignes affichées dans la sortie sur 25
pd.set_option("display.max_rows", 25)

Vous pouvez les ajouter à un fichier de démarrage pour les définir automatiquement à chaque démarrage de votre interprète.

Dans ce tutoriel, vous allez vous concentrer sur trois jeux de données:

  1. Le jeu de données du Congrès américain contient des informations publiques sur les membres historiques du Congrès et illustre plusieurs capacités fondamentales du .par groupe().
  2. L'ensemble de données sur la qualité de l'air contient des lectures périodiques du capteur de gaz. Cela vous permettra de travailler avec des données flottantes et des séries chronologiques.
  3. L'ensemble de données d'agrégateur de nouvelles qui contient des métadonnées sur plusieurs centaines de milliers d'articles. Vous travaillerez avec des chaînes et ferez du texte groupé.

Vous pouvez télécharger le code source de tous les exemples de ce tutoriel en cliquant sur le lien ci-dessous:

Une fois que vous avez téléchargé le .Zip *: français, vous pouvez le décompresser dans votre répertoire actuel:

$ décompressez -q -d groupby-data groupby-data.zip

le -ré Cette option vous permet d’extraire le contenu dans un nouveau dossier:

./
│
└── groupby-data /
    │
    ├── legislators-historical.csv
    Qual airqual.csv
    └── news.csv

Avec cette configuration, vous êtes prêt à intervenir!

Exemple 1: Jeu de données du Congrès américain

Vous allez entrer directement dans les choses en disséquant un ensemble de données de membres historiques du Congrès. Vous pouvez lire le fichier CSV dans un Pandas Trame de données avec read_csv ():

importation pandas comme pd

types = 
    "Prénom": "Catégorie",
    "Le sexe HOMME ou FEMME": "Catégorie",
    "type": "Catégorie",
    "Etat": "Catégorie",
    "fête": "Catégorie",

df = pd.read_csv(
    "groupby-data / legislators-historical.csv",
    type=types,
    Usecols=liste(types) + [[[["anniversaire", "nom de famille"],
    parse_dates=[[[["anniversaire"]
)

Le jeu de données contient les noms et prénoms des membres, leur date de naissance, leur sexe, leur type ("représentant" pour la Chambre des représentants ou "sen" pour le Sénat), l’État américain et le parti politique. Vous pouvez utiliser df.tail () pour afficher les dernières lignes du jeu de données:

>>>

>>> df.queue()
                        prénom_nom prénom_anniversaire anniversaire genre type État partie
11970 Garrett Thomas 1972-03-27 Représentant VA Républicain
11971 Handel Karen 1962-04-18 représentant rep républicain
11972 Jones Brenda 1959-10-24 Représentant F Démocrate MI
11973 Marino Tom 1952-08-15 Représentant PA Républicain
11974 Jones Walter 1943-02-10 Représentant NC Républicain

le Trame de données utilise catégorique types pour l'efficacité de l'espace:

>>>

>>> df.types
nom de famille
catégorie prénom
date d'anniversaire64[ns]
catégorie de genre
type de catégorie
catégorie d'état
catégorie de parti
dtype: objet

Vous pouvez voir que la plupart des colonnes de l’ensemble de données ont le type Catégorie, ce qui réduit la charge de mémoire sur votre machine.

Le “Hello, World!” De Pandas GroupBy

Maintenant que vous êtes familiarisé avec l'ensemble de données, vous commencerez par «Hello, World!» Pour l'opération Pandas GroupBy. Quel est le nombre de membres du Congrès, État par État, sur l’ensemble de l’historique de l’ensemble de données? En SQL, vous pouvez trouver cette réponse avec un SÉLECTIONNER déclaration:

SÉLECTIONNER Etat, compter(Nom)
DE df
GROUPE PAR Etat
ORDRE PAR Etat;

Voici l'équivalent quasi-équivalent chez les Pandas:

>>>

>>> n_by_state = df.par groupe("Etat")[[[["nom de famille"].compter()
>>> n_by_state.tête(dix)
Etat
AK 16
AL 206
AR 117
AS 2
AZ 48
CA 361
CO 90
CT 240
DC 2
DE 97
Nom: nom de famille, type: int64

Tu appelles .par groupe() et passez le nom de la colonne que vous souhaitez grouper, qui est "Etat". Ensuite, vous utilisez ["last_name"] pour spécifier les colonnes sur lesquelles vous voulez effectuer l'agrégation réelle.

Vous pouvez transmettre beaucoup plus qu'un simple nom de colonne à .par groupe() comme premier argument. Vous pouvez également spécifier l’un des éléments suivants:

  • UNE liste de plusieurs noms de colonnes
  • UNE dict ou des pandas Séries
  • Un tableau NumPy ou des pandas Indice, ou un tableau semblable à ceux-ci

Voici un exemple de regroupement conjoint sur deux colonnes, qui répertorie le nombre de membres du Congrès ventilé par État, puis par sexe:

>>>

>>> df.par groupe([[[["Etat", "Le sexe HOMME ou FEMME"])[[[["nom de famille"].compter()
sexe de l'état
AK M 16
AL F 3
                            M 203
AR F 5
                            M 112
                                                                ...
WI M 196
WV F 1
                            M 119
WY F 2
                            M 38
Nom: nom, longueur: 104, type: int64

La requête SQL analogue ressemblerait à ceci:

SÉLECTIONNER Etat, Le sexe HOMME ou FEMME, compter(Nom)
DE df
GROUPE PAR Etat, Le sexe HOMME ou FEMME
ORDRE PAR Etat, Le sexe HOMME ou FEMME;

Comme vous le verrez ensuite, .par groupe() et les instructions SQL comparables sont des cousins ​​proches, mais elles ne sont pas fonctionnellement identiques.

Pandas GroupBy vs SQL

C'est le bon moment pour introduire une différence importante entre l'opération Pandas GroupBy et la requête SQL ci-dessus. Le jeu de résultats de la requête SQL contient trois colonnes:

  1. Etat
  2. Le sexe HOMME ou FEMME
  3. compter

Dans la version Pandas, les colonnes groupées sont poussées dans la MultiIndex de la résultante Séries par défaut:

>>>

>>> n_by_state_gender = df.par groupe([[[["Etat", "Le sexe HOMME ou FEMME"])[[[["nom de famille"].compter()
>>> type(n_by_state_gender)

>>> n_by_state_gender.indice[:[:[:[:5]
MultiIndex ([('AK''M')[('AK''M')[('AK''M')[('AK''M')
                                                ('AL', 'F'),
                                                ('AL', 'M'),
                                                ('AR', 'F'),
                                                ('BRAS')],
                                            noms =['state', 'gender'])

Pour émuler plus étroitement le résultat SQL et repousser les colonnes groupées sur dans les colonnes du résultat, vous utilisez as_index = False:

>>>

>>> df.par groupe([[[["Etat", "Le sexe HOMME ou FEMME"], as_index=Faux)[[[["nom de famille"].compter()
                indiquer le genre last_name
0 AK F NaN
1 AK M 16.0
2 AL F 3.0
3 AL M 203.0
4 AR F 5.0
... ... ... ...
111 WI M 196.0
112 WV 1.0
113 WV M 119,0
114 WY F 2.0
115 WY M 38.0

[116 rows x 3 columns]

Cela produit un Trame de données avec trois colonnes et un RangeIndex, Plutôt qu'un Séries avec un MultiIndex. En bref, en utilisant as_index = False rendra votre résultat plus proche de la sortie SQL par défaut pour une opération similaire.

Notez également que les requêtes SQL ci-dessus utilisent explicitement COMMANDÉ PAR, tandis que .par groupe() ne fait pas. C'est parce que .par groupe() fait-il cela par défaut à travers son paramètre Trier, lequel est Vrai sauf indication contraire de votre part:

>>>

>>> # Ne pas trier les résultats par les clés de tri
>>> df.par groupe("Etat", Trier=Faux)[[[["nom de famille"].compter()
Etat
DE 97
VA 432
SC 251
MD 305
PA 1053
                        ...
AK 16
PI 13
VI 4
GU 4
AS 2
Nom: nom, longueur: 58, type: int64

Ensuite, vous plongerez dans l’objet qui .par groupe() produit effectivement.

Comment Pandas GroupBy fonctionne

Avant d’entrer dans les détails, reculez pour regarder. .par groupe() lui-même:

>>>

>>> par_etat = df.par groupe("Etat")
>>> impression(par_etat)

Qu'est-ce que c'est DataFrameGroupBy chose? Ses .__ str __ () ne vous donne pas beaucoup d’informations sur ce qu’il est réellement ou comment cela fonctionne. La raison pour laquelle DataFrameGroupBy L’objet peut être difficile à comprendre, c’est que c’est paresseux dans la nature. Il n’existe aucune opération permettant de produire un résultat utile tant que vous ne le dites pas.

Un terme fréquemment utilisé à côté de .par groupe() est split-apply-combine. Cela fait référence à une chaîne de trois étapes:

  1. Divisé une table en groupes
  2. Appliquer certaines opérations à chacune de ces petites tables
  3. Combiner Les resultats

Il peut être difficile d'inspecter df.groupby ("state") car il ne fait pratiquement rien de tout cela jusqu'à ce que vous fassiez quelque chose avec l'objet obtenu. Encore une fois, un objet Pandas GroupBy est paresseux. Il retarde pratiquement chaque partie du processus de combinaison application / division jusqu'à ce que vous invoquiez une méthode dessus.

Alors, comment pouvez-vous séparer mentalement la scission, appliquer et combiner des étapes si vous ne pouvez voir aucune d’elles se dérouler de manière isolée? Une méthode utile pour inspecter un objet Pandas GroupBy et voir la division en action consiste à effectuer une itération sur celui-ci. Ceci est mis en œuvre dans DataFrameGroupBy .__ iter __ () et produit un itérateur de (groupe, Trame de données) paires pour DataFrames:

>>>

>>> pour Etat, Cadre dans par_etat:
...     impression(F"2 premières entrées pour état! r")
...     impression("------------------------")
...     impression(Cadre.tête(2), fin=" n  n")
...
2 premières entrées pour 'AK'
------------------------
                    prénom_nom prénom_anniversaire anniversaire genre type État partie
6619 Waskey Frank 1875-04-20 Représentant AK Démocrate
6647 Cale Thomas 1848-09-17 Représentant AK Indépendant

2 premières entrées pour 'AL'
------------------------
                prénom_nom prénom_anniversaire anniversaire genre type État partie
912 Crowell John 1780-09-18 rep rep républicain
991 Walker John 1783-08-12 M sen AL Républicain

Si vous travaillez sur un problème d’agrégation difficile, itérer sur l’objet Pandas GroupBy peut être un excellent moyen de visualiser la Divisé partie de split-apply-combine.

Il existe quelques autres méthodes et propriétés qui vous permettent d'examiner les groupes individuels et leurs divisions. le .groupes attribut vous donnera un dictionnaire de nom du groupe: étiquette du groupe paires. Par exemple, par_etat est un dict avec des états comme clés. Voici la valeur pour le "PENNSYLVANIE" clé:

>>>

>>> par_etat.groupes[[[["PENNSYLVANIE"]
Int64Index ([41921273857697684[41921273857697684[41921273857697684[41921273857697684
                                                            88
                                                ...
                                                11842, 11866, 11875, 11877, 11887, 11891, 11932, 11945, 11959,
                                                11973],
                                            dtype = 'int64', length = 1053)

Chaque valeur est une séquence des emplacements d'index pour les lignes appartenant à ce groupe particulier. Dans la sortie ci-dessus, 4, 19, et 21 sont les premiers indices dans df à laquelle l'état est égal à «PA».

Vous pouvez aussi utiliser .get_group () pour accéder à la sous-table à partir d'un seul groupe:

>>>

>>> par_etat.get_group("PENNSYLVANIE")
                        prénom_nom prénom_anniversaire anniversaire genre type État partie
4 Clymer George 1739-03-16 Représentant M NaN
19 Maclay William 1737-07-20 M sen PA anti-administration
21 Morris Robert 1734-01-20 M sen PA Pro-Administration
27 Wynkoop Henry 1737-03-02 représentant rep PA NaN
38 Jacobs Israel 1726-06-09 Représentant PA NaN
...         ...        ...        ...    ...  ...   ...                  ...
11891 Brady Robert 1945-04-07 représentant représentant démocrate
11932 Shuster Bill 1961-01-10 Représentant PA Républicain
11945 Rothfus Keith 1962-04-25 Représentant PA Républicain
11959 Costello Ryan 1976-09-07 Représentant PA Républicain
11973 Marino Tom 1952-08-15 Représentant PA Républicain

Ceci est pratiquement équivalent à utiliser .loc[]. Vous pourriez obtenir le même résultat avec quelque chose comme df.loc[df["state"] == "PA"].

Ensuite, qu'en est-il de la appliquer partie? Vous pouvez considérer cette étape du processus comme appliquant la même opération (ou appelable) à chaque "sous-table" générée par l'étape de fractionnement. (Je ne sais pas si “sous-table” est le terme technique, mais je n’en ai pas trouvé de meilleur)

À partir de l'objet Pandas GroupBy par_etat, vous pouvez saisir l’état initial des États-Unis et Trame de données avec suivant(). Lorsque vous parcourez un objet Pandas GroupBy, vous obtenez des paires que vous pouvez décompresser en deux variables:

>>>

>>> Etat, Cadre = suivant(iter(par_etat))  # Premier tuple d'itérateur
>>> Etat
'AK'
>>> Cadre.tête(3)
                    prénom_nom prénom_anniversaire anniversaire genre type État partie
6619 Waskey Frank 1875-04-20 Représentant AK Démocrate
6647 Cale Thomas 1848-09-17 Représentant AK Indépendant
7442 Grigsby George 1874-12-02 représentant rep AK NaN

Maintenant, repensez à votre opération initiale complète:

>>>

>>> df.par groupe("Etat")[[[["nom de famille"].compter()
Etat
AK 16
AL 206
AR 117
AS 2
AZ 48
...

le appliquer stade, lorsqu'il est appliqué à votre unique, sous-réglé Trame de données, ressemblerait à ceci:

>>>

>>> Cadre[[[["nom de famille"].compter()  # Compter pour l'état == 'AK'
16

Vous pouvez voir que le résultat, 16, correspond à la valeur de AK dans le résultat combiné.

La dernière étape, combiner, est le plus explicite. Il prend simplement les résultats de toutes les opérations appliquées sur toutes les sous-tables et les combine de manière intuitive.

Exemple 2: Jeu de données sur la qualité de l'air

L'ensemble de données sur la qualité de l'air contient les lectures horaires d'un capteur de gaz en Italie. Les valeurs manquantes sont désignées par -200 dans le fichier CSV. Vous pouvez utiliser read_csv () pour combiner deux colonnes dans un horodatage tout en utilisant un sous-ensemble des autres colonnes:

importation pandas comme pd

df = pd.read_csv(
    "groupby-data / airqual.csv",
    parse_dates=[[[[[[[["Date", "Temps"]],
    na_values=[[[[-200],
    Usecols=[[[["Date", "Temps", "CO (GT)", "T", "RH", "AH"]
).Renommer(
    Colonnes=
        "CO (GT)": "co",
        "Date_Time": "timbre",
        "T": "temp_c",
        "RH": "rel_hum",
        "AH": "abs_hum",
    
).set_index("timbre")

Cela produit un Trame de données avec un DatetimeIndex et quatre flotte Colonnes:

>>>

>>> df.tête()
                                                                                        co temp_c rel_hum abs_hum
timbre
2004-03-10 18:00:00 2.6 13.6 48.9 0.758
2004-03-10 19:00:00 2,0 13,3 47,7 0,726
2004-03-10 20:00:00 2.2 11.9 54.0 0.750
2004-03-10 21:00:00 2.2 11.0 60.0 0.787
2004-03-10 22:00:00 1,6 11,2 59,6 0,789

Ici, co est la lecture moyenne de monoxyde de carbone de cette heure, temp_c, rel_hum, et abs_hum sont la température moyenne en degrés Celsius, l'humidité relative et l'humidité absolue au cours de cette heure, respectivement. Les observations vont de mars 2004 à avril 2005:

>>>

>>> df.indice.min()
Horodatage ('2004-03-10 18:00:00')
>>> df.indice.max()
Horodatage ('2005-04-04 14:00:00')

Jusqu’à présent, vous avez regroupé des colonnes en spécifiant leurs noms str, tel que df.groupby ("state"). Mais .par groupe() est beaucoup plus flexible que cela! Vous verrez comment ensuite.

Regroupement sur des tableaux dérivés

Plus tôt, vous avez vu que le premier paramètre à .par groupe() peut accepter plusieurs arguments différents:

  • Une colonne ou une liste de colonnes
  • UNE dict ou des pandas Séries
  • Un tableau NumPy ou des pandas Indice, ou un tableau semblable à ceux-ci

Vous pouvez profiter de la dernière option pour grouper par jour de la semaine. Vous pouvez utiliser les index .day_name () produire un pandas Indice de cordes. Voici les dix premières observations:

>>>

>>> jour_noms = df.indice.nom_jour()
>>> type(jour_noms)

>>> jour_noms[:[:[:[:dix]
Indice([Mercredi«Mercredi»Mercredi»Mercredi»[Mercredi«Mercredi»Mercredi»Mercredi»['Wednesday''Wednesday''Wednesday''Wednesday''Wednesday'['Wednesday''Wednesday''Wednesday''Wednesday''Wednesday'
                            'Mercredi', 'jeudi', 'jeudi', 'jeudi', 'jeudi'],
                        dtype = 'object', name = 'tstamp')

Vous pouvez ensuite prendre cet objet et l'utiliser comme .par groupe() clé. En Pandas-parler, jour_noms est comme un tableau. C’est une séquence d’étiquettes à une dimension.

Maintenant, passez cet objet à .par groupe() trouver le monoxyde de carbone moyen ()co) lecture par jour de la semaine:

>>>

>>> df.par groupe(jour_noms)[[[["co"].signifier()
timbre
Vendredi 2.543
Lundi 2,017
Samedi 1.861
Dimanche 14h38
Jeudi 2.456
Mardi 2,382
Mercredi 2.401
Nom: co, type: float64

Le processus split-apply-combine se comporte en grande partie de la même manière qu'avant, à l'exception du fait que cette division est effectuée sur une colonne créée artificiellement. Cette colonne n’existe pas dans le DataFrame lui-même, mais en est dérivée.

Et si vous vouliez grouper non seulement par jour de la semaine, mais par heure de la journée? Ce résultat devrait avoir 7 * 24 = 168 observations. Pour ce faire, vous pouvez passer une liste d'objets ressemblant à des tableaux. Dans ce cas, vous passerez des pandas Int64Index objets:

>>>

>>> heure = df.indice.heure
>>> df.par groupe([[[[jour_noms, heure])[[[["co"].signifier().rename_axis([[[["Dow", "heure"])
dow hr
Vendredi 0 1.936
                                            1 1.609
                                            2 1,172
                                            3 0,887
                                            4 0,823
                                                                    ...
Mercredi 19 4.147
                                            20 3,845
                                            21 2,898
                                            22 2,102
                                            23 1.938
Nom: co, Longueur: 168, type: float64

Voici encore un cas similaire qui utilise .Couper() pour séparer les valeurs de température en intervalles discrets:

>>>

>>> bacs = pd.Couper(df[[[["temp_c"], bacs=3, Étiquettes=("cool", "chaud", "chaud"))
>>> df[[[[[[[["rel_hum", "abs_hum"]].par groupe(bacs).agg([[[["signifier", "médian"])
                            rel_hum abs_hum
                                        médiane moyenne médiane
temp_c
cool 57.651 59.2 0.666 0.658
chaud 49.383 49.3 1.183 1.145
chaud 24.994 24.1 1.293 1.274

Dans ce cas, bacs est en fait un Séries:

>>>

>>> type(bacs)

>>> bacs.tête()
timbre
2004-03-10 18:00:00 cool
2004-03-10 19:00:00 cool
2004-03-10 20:00:00 cool
2004-03-10 21:00:00 cool
2004-03-10 22:00:00 cool
Nom: temp_c, dtype: catégorie
Catégories (3, objets): [cool < warm < hot]

Que ce soit un Séries, NumPy tableau, ou la liste n'a pas d'importance. L’important est que bacs sert toujours de séquence d'étiquettes, l'une des cool, chaud, ou chaud. Si tu le voulais vraiment, tu pourrais aussi utiliser un Catégorique tableau ou même un vieux-vieux liste:

  • Liste Python native: df.groupby (bins.tolist ())
  • Tableau catégorique des pandas: df.groupby (bin.values)

Comme vous pouvez le voir, .par groupe() est intelligent et peut gérer beaucoup de types d’entrées différents. N'importe lequel d'entre eux produirait le même résultat, car ils fonctionnent tous comme une séquence d'étiquettes sur lesquelles effectuer le regroupement et le fractionnement.

Rééchantillonnage

Vous avez groupé df par jour de la semaine avec df.groupby (day_names)["co"].signifier(). Considérons maintenant quelque chose de différent. Et si vous vouliez regrouper par année d’observation et par trimestre? Voici un moyen d’y parvenir:

>>>

>>> # Voir une alternative plus facile ci-dessous
>>> df.par groupe([[[[df.indice.année, df.indice.trimestre])[[[["co"].agg(
...     [[[["max", "min"]
... ).rename_axis([[[["année", "trimestre"])
                                                            maximum minimum
trimestre de l'année
2004 1 8,1 0,3
                    2 7,3 0,1
                    3 7,5 0,1
                    4 11,9 0,1
2005 1 8,7 0,1
                    2 5,0 0,3

Toute cette opération peut aussi être exprimée par le biais de rééchantillonnage. Une des utilisations du ré-échantillonnage est comme groupe basé sur le temps. Tout ce que vous devez faire est de passer une chaîne de fréquence, telle que "Q" pour "trimestriel", et les pandas feront le reste:

>>>

>>> df.rééchantillonner("Q")[[[["co"].agg([[[["max", "min"])
                                                    maximum minimum
timbre
2004-03-31 8.1 0.3
2004-06-30 7.3 0.1
2004-09-30 7,5 0,1
2004-12-31 11,9 0,1
2005-03-31 8,7 0,1
2005-06-30 5.0 0.3

Souvent, lorsque vous utilisez .resample () vous pouvez exprimer des opérations de regroupement basées sur le temps de manière beaucoup plus succincte. Le résultat peut être un peu différent de celui plus détaillé .par groupe() équivalent, mais vous constaterez souvent que .resample () vous donne exactement ce que vous recherchez.

Exemple 3: jeu de données d'agrégateur de nouvelles

Vous allez maintenant travailler avec le troisième et dernier jeu de données, qui contient des métadonnées sur plusieurs centaines de milliers d'articles de nouvelles et les regroupe en groupes de sujets:

importation date / heure comme dt
importation pandas comme pd

def parse_millisecond_timestamp(ts: int) -> dt.date / heure:
    "" "Convertissez ms depuis l'époque Unix en instance datetime UTC." ""
    revenir dt.date / heure.à partir de l'horodatage(ts / 1000, tz=dt.fuseau horaire.UTC)

df = pd.read_csv(
    "groupby-data / news.csv",
    SEP=" t",
    entête=Aucun,
    index_col=0,
    des noms=[[[["Titre", "url", "sortie", "Catégorie", "grappe", "hôte", "timbre"],
    parse_dates=[[[["timbre"],
    date_parser=parse_millisecond_timestamp,
    type=
        "sortie": "Catégorie",
        "Catégorie": "Catégorie",
        "grappe": "Catégorie",
        "hôte": "Catégorie",
    ,
)

Pour le lire en mémoire avec le bon dyptes, vous avez besoin d’une fonction d’aide pour analyser la colonne d’horodatage. En effet, il s’agit du nombre de millisecondes écoulées depuis l’époque Unix, plutôt que de quelques fractions de seconde, ce qui est la convention. Semblable à ce que vous avez fait auparavant, vous pouvez utiliser le logiciel catégorique. type coder efficacement les colonnes qui ont un nombre relativement petit de valeurs uniques par rapport à la longueur de la colonne.

Chaque ligne du jeu de données contient le titre, l’URL, le nom du point de publication, le domaine, ainsi que l’horodatage de publication. grappe est un identifiant aléatoire pour le cluster de sujets auquel un article appartient. Catégorie est la catégorie nouvelles et contient les options suivantes:

  • b pour le business
  • t pour la science et la technologie
  • e pour le divertissement
  • m pour la santé

Voici la première rangée:

>>>

>>> df.iloc[[[[0]
titre officiel de la Fed dit wea ...
URL http: //www.latimes.co ...
outlet Los Angeles Times
catégorie b
cluster ddUyU0VZz0BRneMioxUPQ ...
hébergeur www.latimes.com
tstamp 2014-03-10 16: 52: 50.6 ...
Nom: 1, type: objet

Maintenant que vous avez un aperçu des données, vous pouvez commencer à poser des questions plus complexes à ce sujet.

Utiliser les fonctions Lambda dans .par groupe()

Cet ensemble de données invite beaucoup plus de questions potentiellement impliquées. Je vais en lancer un, aléatoire mais significatif: quels médias parlent le plus de la Réserve fédérale? Supposons, pour simplifier, que cela implique de rechercher des mentions sensibles à la casse de "Nourris". N'oubliez pas que cela peut générer des faux positifs avec des termes tels que «gouvernement fédéral».

Pour compter les mentions par point de vente, vous pouvez appeler .par groupe() sur la sortie, puis littéralement .appliquer() une fonction sur chaque groupe:

>>>

>>> df.par groupe("sortie", Trier=Faux)[[[["Titre"].appliquer(
...     lambda ser: ser.str.contient("Nourris").somme()
... ).plus grand(dix)
sortie
Reuters 161
NASDAQ 103
Semaine d'affaires 93
Investing.com 66
Wall Street Journal  (blog ) 61
MarketWatch 56
Moneynews 55
Bloomberg 53
GlobalPost 51
Economic Times 44
Nom: titre, type: int64

Décomposons cela car il y a plusieurs appels de méthode effectués successivement. Comme auparavant, vous pouvez extraire le premier groupe et son objet Pandas correspondant en prenant le premier tuple du groupe PandasPar itérateur:

>>>

>>> Titre, ser = suivant(iter(df.par groupe("sortie", Trier=Faux)[[[["Titre"]))
>>> Titre
'Los Angeles Times'
>>> ser.tête()
Un responsable de la Fed a déclaré que les données météorologiques ...
486 actions tombent sur des nouvelles décourageantes en provenance d'Asie
1124 Indices de la montée de Gengis Khan, écrits dans l ...
1146 Les éléphants distinguent les voix humaines par sexe, âge ...
1237 Honda divise Acura en une division distincte pour ...
Nom: titre, type: objet

Dans ce cas, ser est un pandas Séries Plutôt qu'un Trame de données. C’est parce que vous avez suivi la .par groupe() appeler avec ["title"]. Cela sélectionne effectivement cette colonne unique dans chaque sous-table.

Vient ensuite .str.contains ("Fed"). Cela retourne un booléen Séries C'est Vrai lorsqu'un titre d'article enregistre une correspondance dans la recherche. Effectivement, la première ligne commence par "Un responsable de la Fed dit que les données sont faibles en raison de la météo, ..." et s'allume comme Vrai:

>>>

>>> ser.str.contient("Nourris")
1 vrai
486 Faux
1124 Faux
1146 Faux
1237 Faux
                                        ...
421547 Faux
421584 Faux
421972 Faux
422226 Faux
422905 Faux
Nom: titre, longueur: 1976, type: bool

La prochaine étape consiste à .somme() cette Séries. Puisque bool est techniquement juste un type spécialisé de int, vous pouvez résumer un Séries de Vrai et Faux tout comme vous résumer une séquence de 1 et 0:

>>>

>>> ser.str.contient("Nourris").somme()
17

Le résultat est le nombre de mentions de "Nourris" par le Los Angeles Times dans le jeu de données. La même procédure est appliquée pour Reuters, NASDAQ, Businessweek et le reste du lot.

Améliorer la performance de .par groupe()

Revenons à nouveau à .groupby (...). apply () pour voir pourquoi ce modèle peut être sous-optimal. Pour obtenir des informations de base, consultez Comment accélérer vos projets Pandas. Que peut-il arriver avec .appliquer() est qu'il va effectivement effectuer une boucle Python sur chaque groupe. Tandis que le .groupby (...). apply () pattern peut fournir une certaine flexibilité, il peut également empêcher Pandas d’utiliser ses optimisations basées sur Cython.

Tout cela pour dire que chaque fois que vous vous trouvez à penser à utiliser .appliquer(), demandez-vous s’il existe un moyen d’exprimer l’opération de manière vectorielle. Dans ce cas, vous pouvez tirer parti du fait que .par groupe() accepte non seulement un ou plusieurs noms de colonnes, mais aussi de nombreux comme un tableau structures:

  • Un tableau NumPy à 1 dimension
  • Une liste
  • Un pandas Séries ou Indice

Notez également que .par groupe() est une méthode d'instance valide pour un Séries, pas seulement un Trame de données, donc vous pouvez essentiellement inverser la logique de division. Dans cet esprit, vous pouvez d’abord construire un Séries de booléens indiquant si le titre contient ou non "Nourris":

>>>

>>> mentions_fed = df[[[["Titre"].str.contient("Nourris")
>>> type(mentions_fed)

Maintenant, .par groupe() est aussi une méthode de Sériespour pouvoir en grouper un Séries sur un autre:

>>>

>>> importation numpy comme np
>>> mentions_fed.par groupe(
...     df[[[["sortie"], Trier=Faux
... ).somme().plus grand(dix).type(np.uintc)
sortie
Reuters 161
NASDAQ 103
Semaine d'affaires 93
Investing.com 66
Wall Street Journal  (blog ) 61
MarketWatch 56
Moneynews 55
Bloomberg 53
GlobalPost 51
Economic Times 44
Nom: titre, type: uint32

Les deux Séries ne pas avoir besoin d'être des colonnes de la même Trame de données objet. Ils doivent simplement avoir la même forme:

>>>

>>> mentions_fed.forme
(422419,)
>>> df[[[["sortie"].forme
(422419,)

Enfin, vous pouvez convertir le résultat en un entier non signé avec np.uintc si vous êtes déterminé à obtenir le résultat le plus compact possible. Voici une comparaison tête-à-tête des deux versions qui produiront le même résultat:

# Version 1: utilisation de `.apply ()`
df.par groupe("sortie", Trier=Faux)[[[["Titre"].appliquer(
    lambda ser: ser.str.contient("Nourris").somme()
).plus grand(dix)

# Version 2: utilisation de la vectorisation
mentions_fed.par groupe(
    df[[[["sortie"], Trier=Faux
).somme().plus grand(dix).type(np.uintc)

Sur mon ordinateur portable, la version 1 prend 4,01 secondes, tandis que la version 2 ne prend que 292 millisecondes. C'est une différence impressionnante de 14 fois le temps de calcul pour quelques centaines de milliers de lignes. Considérez à quel point la différence devient dramatique lorsque votre ensemble de données atteint quelques millions de lignes!

Pandas GroupBy: Tout rassembler

Si vous appelez dir () sur un objet Pandas GroupBy, vous y verrez suffisamment de méthodes pour vous faire tourner la tête! Il peut être difficile de suivre toutes les fonctionnalités d’un objet Pandas GroupBy. Une façon de dissiper le brouillard consiste à compartimenter les différentes méthodes en ce qu’elles font et comment elles se comportent.

De manière générale, les méthodes d'un objet Pandas GroupBy appartiennent à une poignée de catégories:

  1. Méthodes d'agrégation (aussi appelé méthodes de réduction) “Smush” de nombreux points de données dans une statistique agrégée sur ces points de données. Par exemple, prenons la somme, la moyenne ou la médiane de 10 nombres, pour lesquels le résultat n’est qu’un seul chiffre.

  2. Méthodes de filtrage revenir à vous avec un sous-ensemble de l'original Trame de données. Cela signifie le plus souvent en utilisant .filtre() supprimer des groupes entiers en se basant sur des statistiques comparatives concernant ce groupe et son sous-tableau. Il est également judicieux d’inclure dans cette définition un certain nombre de méthodes permettant d’exclure des lignes particulières de chaque groupe.

  3. Méthodes de transformation retourner un Trame de données avec la même forme et les mêmes indices que l'original, mais avec des valeurs différentes. Avec les méthodes d’agrégation et de filtrage, le résultat Trame de données sera généralement de plus petite taille que l'entrée Trame de données. Ce n'est pas le cas d'une transformation qui transforme les valeurs individuelles elles-mêmes tout en conservant la forme de l'original. Trame de données.

  4. Méta méthodes sont moins concernés par l'objet original sur lequel vous avez appelé .par groupe(), et plus axé sur la fourniture d’informations de haut niveau telles que le nombre de groupes et les indices de ces groupes.

  5. Méthodes de traçage imiter l'API de tracer pour un pandas Séries ou Trame de données, mais généralement diviser la sortie en plusieurs sous-parcelles.

La documentation officielle a sa propre explication de ces catégories. Elles sont, dans une certaine mesure, sujettes à interprétation et ce didacticiel pourrait diverger légèrement en classant quelle méthode tombe où.

Vous pouvez consulter une ventilation plus détaillée de chaque catégorie et des différentes méthodes de .par groupe() qui tombent sous eux:

Méthodes d'agrégation (aussi appelé méthodes de réduction) “Smush” de nombreux points de données dans une statistique agrégée sur ces points de données. Par exemple, prenons la somme, la moyenne ou la médiane de 10 nombres, pour lesquels le résultat n’est qu’un seul chiffre. Voici quelques méthodes d'agrégation:

Méthodes de filtrage revenir à vous avec un sous-ensemble de l'original Trame de données. Cela signifie le plus souvent en utilisant .filtre() supprimer des groupes entiers en se basant sur des statistiques comparatives concernant ce groupe et son sous-tableau. Il est également judicieux d’inclure dans cette définition un certain nombre de méthodes permettant d’exclure des lignes particulières de chaque groupe. Voici quelques méthodes de filtrage:

Méthodes de transformation retourner un Trame de données avec la même forme et les mêmes indices que l'original, mais avec des valeurs différentes. Avec les méthodes d’agrégation et de filtrage, le résultat Trame de données sera généralement de plus petite taille que l'entrée Trame de données. Ce n'est pas le cas d'une transformation qui transforme les valeurs individuelles elles-mêmes tout en conservant la forme de l'original. Trame de données. Voici quelques méthodes de transformation:

Méta méthodes sont moins concernés par l'objet original sur lequel vous avez appelé .par groupe(), et plus axé sur la fourniture d’informations de haut niveau telles que le nombre de groupes et les indices de ces groupes. Voici quelques méthodes méta:

Il existe quelques méthodes d’objets Pandas GroupBy qui ne tombent pas bien dans les catégories ci-dessus. Ces méthodes produisent généralement un objet intermédiaire qui est ne pas une Trame de données ou Séries. Par exemple, df.groupby (...). roulant (...) produit un RollingGroupby objet, que vous pouvez ensuite appeler des méthodes d'agrégation, de filtrage ou de transformation sur:

  • .expansion()
  • .tuyau()
  • .resample ()
  • .roulant()

Conclusion

Dans ce tutoriel, vous avez parcouru une tonne de terrain sur .par groupe(), y compris sa conception, son API et comment enchaîner des méthodes pour obtenir des données dans une sortie adaptée à vos besoins.

Vous avez appris:

  • Comment utiliser les opérations Pandas GroupBy sur des données réelles
  • Comment le split-apply-combine chaîne d'opérations fonctionne et comment vous pouvez la décomposer en étapes
  • Comment les méthodes d'un Pandas GroupBy peuvent être classées dans différentes catégories en fonction de leur intention et de leur résultat

Il y a beaucoup plus à .par groupe() que vous pouvez couvrir dans un tutoriel. Check out the resources below and use the example datasets here as a starting point for further exploration!

You can download the source code for all the examples in this tutorial by clicking on the link below:

More Resources on Pandas GroupBy

Pandas documentation guides are user-friendly walk-throughs to different aspects of Pandas. Here are some portions of the documentation that you can check out to learn more about Pandas GroupBy:

The API documentation is a fuller technical reference to methods and objects: