Structure du DataFrame¶

Pandas est très utilisé pour sa capacité à gérer des tableaux de données, comme un super tableur. Non seulement il offre de nombreux outils pour manipuler les données mais il est aussi très performant à savoir rapide et pouvant gérer de grosses données.

Pour commencer voici un simple tableau :

In [1]:
import pandas as pd
from IPython.display import display, HTML

CSS = """
.output {
    flex-direction: row;
}
"""
HTML('<style>{}</style>'.format(CSS))
Out[1]:
In [2]:
df = pd.DataFrame({'prix': [1.1, 0.8, 0.6], 'stock': [0, 22, 51]},  # columns are stored in a dictionnary
                  index = ['stylo','crayon','gomme'])

# another way to do the same dataframe. The list shape is index x columns:

df_bis = pd.DataFrame([[1.1, 0], [0.8, 22], [0.6, 51]],
                      index = ['stylo','crayon','gomme'],
                      columns = ['prix', 'stocks'])
display(df, df_bis)
prix stock
stylo 1.1 0
crayon 0.8 22
gomme 0.6 51
prix stocks
stylo 1.1 0
crayon 0.8 22
gomme 0.6 51

Pour bien comprendre ce qu'est un tableau, un DataFrame dans le langage de Pandas, il faut le voir comme un ensemble Series, une série étant une liste de valeurs avec un index.

Dans le cas précédent il y a 2 Series à savoir le prix et les stock, chaque série étant indéxée par la liste des objets de mon inventaire.

Voici comment on faire ressortir les séries :

In [3]:
print(df.prix, '\n')
print(df['prix'], '\n')     # use brackets when the name is a keyword or has spaces or accents
print(type(df.prix))
stylo     1.1
crayon    0.8
gomme     0.6
Name: prix, dtype: float64 

stylo     1.1
crayon    0.8
gomme     0.6
Name: prix, dtype: float64 

<class 'pandas.core.series.Series'>

On peut créer un DataFrame à partir de Series et il se débrouille comme il peut si les séries ont des index différents (en particulier il met des Not A Number, NaN, dans les cases vides) :

In [4]:
df2 = pd.DataFrame({'prix': df.prix,    # we copy column 'prix' of df
                    'stock': df.stock, 
                    'promo': pd.Series([0.2,0,0.05], index=['stylo','papier','crayon']) # Series with different index
                   })
df2
Out[4]:
prix stock promo
crayon 0.8 22.0 0.05
gomme 0.6 51.0 NaN
papier NaN NaN 0.00
stylo 1.1 0.0 0.20

On voit donc qu'une colonne n'est pas constituée que de valeurs mais a un index et des values :

In [5]:
print(df2.prix.index)
print(df2.prix.values)
Index(['crayon', 'gomme', 'papier', 'stylo'], dtype='object')
[0.8 0.6 nan 1.1]

comme le tableau lui-même :

In [6]:
print(df2.index)
print(df2.values)
Index(['crayon', 'gomme', 'papier', 'stylo'], dtype='object')
[[8.0e-01 2.2e+01 5.0e-02]
 [6.0e-01 5.1e+01     nan]
 [    nan     nan 0.0e+00]
 [1.1e+00 0.0e+00 2.0e-01]]

Un tableau a aussi des noms pour les colonnes :

In [7]:
df2.columns
Out[7]:
Index(['prix', 'stock', 'promo'], dtype='object')

No description has been provided for this image

Sélectionner une partie du tableau¶

Le piège de l'indexation (loc et iloc)¶

Pour Pandas les attributs des DataFrames sont les Series, donc les colonnes et non pas les lignes. Ainsi on ne peut pas demander aussi simplement les informations sur les gommes :

In [8]:
df.gomme
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_701/1153377621.py in ?()
----> 1 df.gomme

/opt/conda/lib/python3.11/site-packages/pandas/core/generic.py in ?(self, name)
   6292             and name not in self._accessors
   6293             and self._info_axis._can_hold_identifiers_and_holds_name(name)
   6294         ):
   6295             return self[name]
-> 6296         return object.__getattribute__(self, name)

AttributeError: 'DataFrame' object has no attribute 'gomme'

Il faut utiliser la méthode loc. Cette méthode permet d'extraire

  • une valeur,
  • une ligne ou colonne éventuellement partiellement
  • un sous-tableau.

La méthode iloc fait la même chose mais avec des indices numérotés (integer).

In [9]:
df2.loc['crayon']
Out[9]:
prix      0.80
stock    22.00
promo     0.05
Name: crayon, dtype: float64

Pour spécifier une partie on utilise les [mêmes conventions que pour Numpy](../lesson4 Numpy/np02 Filtres.ipynb).

In [10]:
df2.loc[['gomme','crayon'],:'stock']
Out[10]:
prix stock
gomme 0.6 51.0
crayon 0.8 22.0
In [11]:
print(df2.iloc[0,0])
df2.iloc[0:2, :]
0.8
Out[11]:
prix stock promo
crayon 0.8 22.0 0.05
gomme 0.6 51.0 NaN

Bien sûr on peut extraire les séries puis demander certains indices de ces séries :

In [12]:
df2[['prix','stock']][0:2] # may seems more natural but is less efficient
Out[12]:
prix stock
crayon 0.8 22.0
gomme 0.6 51.0

Optimisation (at & iat)¶

Si on désire récupérer une seule valeur du tableau, at et iat sont plus rapides que loc et iloc.

In [13]:
print(df2.at['gomme', 'stock'])   # same than loc['gomme', 'stock'] buy faster
print(df2.iat[1, 0])      
51.0
0.6

Les filtres logiques¶

On peut aussi utiliser les filtres logiques comme pour Numpy :

In [14]:
df2.loc[df2.promo > 0, 'promo'] += 0.10  # we add 10% sales for products already in sales
df2
Out[14]:
prix stock promo
crayon 0.8 22.0 0.15
gomme 0.6 51.0 NaN
papier NaN NaN 0.00
stylo 1.1 0.0 0.30

Query¶

Pandas propose aussi query pour faire une extraction d'un tableau (en copie) :

In [15]:
df2.query('0.1 < promo < 0.3')
Out[15]:
prix stock promo
crayon 0.8 22.0 0.15

On note que l'écriture est simplifiée par rapport à la facon usuelle lorsqu'on désire faire un filtre compliqué. Voici quelques cas de filtres de query avec un tableau qui a des colonnes A, B et C qui soulignent son utilité :

query Python
'A in B' df[df.A.isin(df.B)]
'A not in B' df[~df.A.isin(df.B)]
'C in [1,2,4,8] df[df.C.isin([1,2,4,8])]
'A == 3 and A > B > C' df[(df['A'] == 3) & (df['A'] > df['B']) & (df['B'] > df['C'])]

Par contre il n'est pas simple d'utiliser query pour modifier un tableau. Ainsi modifier les valeurs de promotion comme fait ci-dessus s'´ecrit :

In [16]:
df2.loc[df2.query('promo > 0').index, 'promo'] += 0.1   # it includes a partial copy of df2

Agir globalement¶

On préfère ne pas travailler sur les éléments mais sur les colonnes globalement avec les méthodes adaptées pour éviter de faire des boucles sur les éléments qui ralentissent l'exécution et compliquent le programme.

Il faut penser en terme de vecteurs et non plus en terme de valeurs. Le plus souvent on peut arriver au résultat souhaité sans faire de boucle (et donc nettement plus rapidement).

In [17]:
print("Stock value = %f\n" % (df.prix * df.stock).sum())
Stock value = 48.200000

On peut appliquer sur une colonne les opérations mathétiques que l'on a vues avec Numpy.

In [18]:
import numpy as np

np.sin(df2.promo)  # just to show that it can be done
Out[18]:
crayon    0.247404
gomme          NaN
papier    0.000000
stylo     0.389418
Name: promo, dtype: float64

Plus¶

Pour plus d'information vous pouvez regarder

  • les lecons suivantes !
  • la documentation de Pandas
  • l'antisèche (que je vous conseille d'avoir sous la main au fur et à mesure des lecons)
In [ ]: