sql vs pandas

Dans cette série d’articles, nous allons tenter de comparer la façon de travailler avec SQL et Pandas. Pour cela, dans ce premier épisode, nous allons sélectionner, filtrer et trier des données à partir d’un jeu de données bien connu.

Introduction

La bibliothèque Pandas de Python et le langage de requêtes SQL sont deux outils populaires pour la manipulation de données et leurs fonctionnalités peuvent parfois se chevaucher. Quel meilleur moyen d’améliorer vos compétences dans les deux domaines que de mettre en concurrence Pandas et SQL en travaillant sur les mêmes problèmes de Data Science pour chacun d’eux ?

Voici le premier article de la série que j’écrirai pour vous aider à comparer directement Pandas et SQL.
Mon objectif est de vous aider :

  1. Apprendre et comparer les fonctionnalités de Pandas versus SQL
  2. Si vous en connaissez déjà un des deux, alors apprendre l’autre
  3. Comprendre les forces et les faiblesses de chacun
  4. Disposer de plusieurs outils pour manipuler les données

Pour les exemples présentés ci-dessous, j’utiliserai le tristement célèbre jeu de données Titanic que l’on peut trouver sur Kaggle (cliquer ici pour le télécharger). Je vous encourage à le regarder pendant que vous lisez cet article pour pouvoir suivre. À la fin, je présenterai quelques problèmes pratiques que vous pourrez résoudre par vous-même en vous basant sur les fonctionnalités spécifiques abordées dans cet article.

Travailler avec SQL et Pandas : Comparaisons

Sélection des colonnes

Commençons par la fonctionnalité la plus élémentaire, à savoir comment sélectionner des colonnes et les afficher dans un certain ordre. Pour notre exercice, disons que nous sommes curieux de voir les colonnes pclass, fare, age, et survived de l’ensemble de données, dans cet ordre.

SQL

En SQL, l’instruction SELECT permet d’énumérer les colonnes que vous souhaitez afficher et dans quel ordre. Nous sélectionnons les colonnes de l’ensemble de données sur le Titanic après qu’il ait été saisi en tant que table dans notre base de données. En SQL, si vous voulez voir toutes les colonnes d’une table, vous devez utiliser SELECT *, mais nous allons nommer les colonnes que nous voulons afficher.

SELECT pclass, fare, age, survived
FROM titanic

Pandas

Dans Pandas, l’ensemble de données titanic serait lu dans Pandas et stocké en tant qu’un objet dataframe (en quelque sorte un tableau de données). A partir de là, nous allons découper les colonnes souhaitées. Il existe plusieurs façons d’obtenir le même résultat.

col_list = ['pclass', 'fare', 'age', 'survived']
titanic[col_list]

Vous pouvez également placer la liste des colonnes directement dans les parenthèses sans créer une variable distincte :

titanic[['pclass', 'fare', 'age', 'survived']]

Une autre méthode consiste à utiliser loc[] :

titanic.loc[:, ['pclass', 'fare', 'age', 'survived']]

Les deux points indiquent que nous voulons sélectionner toutes les lignes, puis après la virgule, nous indiquons quelles colonnes nous voulons et dans quel ordre. Là encore, vous pouvez créer une liste de colonnes sous forme de variable et la placer après la virgule. En général, je préfère créer une variable, car c’est plus propre et il y a moins de crochets dans des crochets, le tout sur une seule ligne. Il est également plus facile de respecter les directives PEP-8 en utilisant une liste de variables, car vous pouvez éviter que vos lignes de code ne deviennent trop longues. À l’avenir, je continuerai à utiliser loc[] et à créer des variables distinctes.

Il convient également de noter que la méthode loc[] de Pandas permet de sélectionner une plage de lignes en fonction de l’index. Si nous voulions examiner uniquement les valeurs d’index 50 à 100 dans l’ensemble de données Titanic, nous pourrions mettre 50:100 comme premier terme et cette plage inclusive de lignes serait la seule à être retournée. Nous pouvons également utiliser les deux points après la virgule pour sélectionner toutes les colonnes ou pour sélectionner une plage de colonnes, comme pour les lignes.

Filtrage basé sur les valeurs des colonnes

Disons maintenant que vous voulez des résultats basés sur une condition spécifique. Poursuivons avec le même exemple et disons que nous voulons à nouveau voir les colonnes pclass, fare, age et survived mais maintenant nous voulons seulement les entrées pour les hommes adultes.

SQL

En SQL, l’instruction WHERE permet de filtrer sur la base d’une valeur spécifique dans les colonnes. L’instruction WHERE suit l’instruction FROM. Étant donné que notre problème nécessite de filtrer sur deux conditions, nous allons les énumérer toutes les deux avec un AND entre les deux. Vous pouvez également utiliser OR entre deux conditions si vous souhaitez renvoyer les lignes qui répondent à l’une ou l’autre de ces conditions.

SELECT pclass, fare, age, survived
FROM titanic
WHERE sex = 'male' AND age >= 18

Pandas

Pour Pandas, nous allons créer des variables qui contiennent les deux conditions que nous voulons filtrer. Puis nous utiliserons à nouveau loc[] pour sélectionner les lignes et les colonnes souhaitées.

male_mask = titanic.sex == 'male'
age_mask = titanic.age >= 18
col_list = ['pclass', 'fare', 'age', 'survived']

titanic.loc[male_mask & age_mask, col_list]

Les deux variables de masque sont des listes de valeurs booléennes indiquant si chaque ligne du dataframe remplit cette condition. Vous vous souvenez que lorsque nous utilisions loc[], nous utilisions les deux points pour spécifier que nous voulions toutes les lignes, puis après la virgule, nous spécifiions les colonnes que nous voulions. Eh bien ici, nous utilisons la même fonctionnalité en spécifiant d’abord les lignes que nous voulons, puis après la virgule, les colonnes que nous voulons afficher. Comme précédemment, vous pouvez introduire les critères de masquage directement sans créer de variables, mais la création de variables rend les choses plus claires. Avec le & entre les variables de masque, seules les lignes qui sont vraies pour les deux conditions seront retournées. Si vous voulez faire de la logique « ou », vous pouvez utiliser le symbole pipe | à la place.

Une petite fonctionnalité intéressante de Pandas est que vous pouvez également utiliser un tilde ~ devant les variables de masque pour inverser les valeurs booléennes. Par exemple, si nous utilisons ~male_mask pour filtrer, tous les booléens True seront inversés en False et vice versa. Dans ce cas, cela reviendrait à filtrer les femmes, mais il peut arriver que cette fonctionnalité soit utile pour pouvoir utiliser la même variable mais en inversant la logique.

Tri des résultats par rapport aux valeurs des colonnes

Poursuivons avec notre problème et disons que nous voulons maintenant que les résultats soient triés d’abord par le montant du tarif et ensuite par l’âge. Et nous voulons que les valeurs tarifaires soient triées par ordre décroissant et les valeurs d’âge par ordre croissant. Cela signifie que toutes les entrées qui ont la même valeur tarifaire seront triées par les individus les plus jeunes en premier.

SQL

En SQL, nous utiliserons l’instruction ORDER BY. L’instruction ORDER BY vient après l’instruction WHERE dans la requête et la méthode de tri par défaut est ascendante. Pour que les résultats soient triés avec les plus grandes valeurs en haut, nous utiliserons le mot-clé DESC.

SELECT pclass, fare, age, survived
FROM titanic
WHERE sex = 'male' AND age >= 18
ORDER BY fare DESC, age

Pandas

Pour Pandas, nous utiliserons sort_values() pour trier. Nous pouvons utiliser une liste de colonnes à trier, la hiérarchie de tri étant dans l’ordre où les colonnes sont listées. Il existe un paramètre ascendant qui est défini sur True par défaut. Nous pouvons passer une liste de valeurs booléennes à ce paramètre, les valeurs correspondant à la colonne respective dans l’ordre où elles sont listées.

male_mask = titanic.sex == 'male'
age_mask = titanic.age >= 18
col_list = ['pclass', 'fare', 'survived']

df_slice = titanic.loc[male_mask & age_mask, col_list]
df_slice.sort_values(['fare', 'age'], ascending=[False, True])

Une remarque importante à faire ici est que si vous prévoyez de modifier des valeurs dans la tranche de dataframe, il est préférable d’enchaîner sur un .copy() après le loc[]. De cette façon, vous pouvez être sûr que vous ne modifiez pas accidentellement le dataframe original. Il peut être vraiment déroutant, même pour ceux qui ont de l’expérience avec Pandas, de connaître les règles qui déterminent si le dataframe d’origine est modifié ou non lorsqu’on effectue des changements sur une tranche.

Limitation aux premiers résultats

Le dernier élément que nous allons ajouter à notre problème pratique est de limiter les résultats aux 10 meilleures entrées.

SQL

Cette fonctionnalité est assez simple avec SQL ; il suffit d’ajouter l’instruction LIMIT et de mettre le nombre d’entrées que nous voulons limiter – dans ce cas 10.

SELECT pclass, fare, age, survived
FROM titanic
WHERE sex = 'male' AND age >= 18
ORDER BY fare DESC, age
LIMIT 10

Pandas

Pour Pandas, nous pouvons prendre les valeurs les plus élevées en enchaînant sur un head() à la fin. Vous passez le nombre d’entrées que vous voulez afficher dans les parenthèses – dans notre cas 10.

male_mask = titanic.sex == 'male'
age_mask = titanic.age >= 18
col_list = ['pclass', 'fare', 'survived']

df_slice = titanic.loc[male_mask & age_mask, col_list]
df_slice.sort_values(['fare', 'age'], ascending=[False, True]).head(10)

Je tiens à souligner ici que Pandas dispose également des méthodes nlargest() et nsmallest() qui peuvent être pratiques pour ordonner les colonnes et limiter les résultats retournés en une seule méthode simple. Cependant, dans notre problème, nous voulions filtrer une colonne dans l’ordre décroissant et une autre colonne dans l’ordre croissant, donc les méthodes nlargest() et nsmallest() ne répondent pas vraiment à nos besoins. Cependant, disons que nous voulons en fait filtrer à la fois fare et age par ordre croissant. Ce serait un cas d’utilisation valide pour nsmallest().

male_mask = titanic.sex == 'male'
age_mask = titanic.age >= 18
col_list = ['pclass', 'fare', 'survived']

df_slice = titanic.loc[male_mask & age_mask, col_list]
df_slice.nsmallest(10, ['fare', 'age'])

Un peu de pratique, à vous de jouer !

  1. Affichez les colonnes age, sex et survived pour les enfants de moins de 16 ans qui n’ont pas de parent voyageant avec eux (une valeur nulle dans la colonne parch). Triez les résultats par ordre croissant d’âge.
  2. Afficher class, age et fare pour les femmes en classe 2 ou 3. Trier les résultats par ordre décroissant d’âge puis de tarif et se limiter aux 20 premières entrées.
  3. Afficher toutes les colonnes pour les enfants (moins de 18 ans) en classe 3 qui ont survécu. Trier les résultats par ordre croissant d’âge puis par ordre décroissant de tarif. La colonne survived contient 1 pour oui et 0 pour non.
Travailler avec SQL et Pandas - Pratique
Attention spoil réponses ci-dessous !

Réponses aux 3 problèmes

J’espère que vous n’avez pas jeté un coup d’œil aux réponses sans au moins essayer de résoudre chaque problème dans votre tête ! Certains problèmes ont plusieurs méthodes de résolution, il est donc normal que vos réponses soient un peu différentes des miennes.

Problème 1

Affichez les colonnes age, sex et survived pour les enfants de moins de 16 ans qui n’ont pas de parent voyageant avec eux (une valeur nulle dans la colonne parch). Triez les résultats par ordre croissant d’âge.

SQL

SELECT age, sex, survived
FROM titanic
WHERE age < 16 AND parch == 0
ORDER BY age

Pandas

children = titanic.age < 16
parents = titanic.parch == 0
col_list = ['age', 'sex', 'survived']

titanic.loc[children & parents, col_list].sort_values('age'

Problème 2

Afficher class, age et fare pour les femmes en classe 2 ou 3. Trier les résultats par ordre décroissant d’âge puis de tarif et se limiter aux 20 premières entrées.

SQL

SELECT pclass, age, fare
FROM titanic
WHERE sex = 'female' AND class != 1
ORDER BY age DESC, fare DESC
LIMIT 20

Pandas

females = titanic.sex == 'female'
lower_class = titanic.pclass != 1
col_list = ['pclass', 'age', 'fare']

df_slice = titanic.loc[females & lower_class, col_list]
df_slice.nlargest(20, ['age', 'fare'])

Problème 3

Afficher toutes les colonnes pour les enfants (moins de 18 ans) en classe 3 qui ont survécu. Trier les résultats par ordre croissant d’âge puis par ordre décroissant de tarif. La colonne survived contient 1 pour oui et 0 pour non.

SQL

SELECT *
FROM titanic
WHERE age < 18 AND pclass = 3 AND survived = 1
ORDER BY age, fare DESC

Pandas

children = titanic.age < 18
low_class = titanic.pclass == 3
survival = titanic.survived == 1

df_slice = titanic.loc[children & low_class & survival, :]
df_slice.sort_values(['age', 'fare'], ascending=[True, False])

Conclusion

Comme vous pouvez le constater, SQL et Pandas sont très différents mais peuvent néanmoins accomplir les mêmes tâches. J’ai pris plaisir à passer mentalement de Pandas à SQL et de SQL à Pandas lors de la rédaction de cet article, j’espère donc que vous l’avez également apprécié.
Si vous voulez lire la suite, c’est par ici : SQL vs Pandas (Partie 2)

Pour aller plus loin, vous pouvez suivre la formation Pandas et la formation SQL.

A lire également