Pandas en 10 minutes

In [1]:
 #Pour intégrer les graphes à votre notebook, il suffit de faire
%matplotlib inline

from jyquickhelper import add_notebook_menu
add_notebook_menu()
Out[1]:
run previous cell, wait for 2 seconds

On importe générallement les librairies suivantes

In [1]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

Création d'objets

On créé une 'Series' en lui passant une liste de valeurs, en laissant pandas créer un index d'entiers

In [3]:
s = pd.Series([1,3,5,np.nan,6,8])
s
Out[3]:
0    1.0
1    3.0
2    5.0
3    NaN
4    6.0
5    8.0
dtype: float64

On créé un DataFrame en passant un array numpy, avec un index sur sur une date et des colonnes labellisées

In [6]:
dates = pd.date_range('20130101', periods=6)
print(dates)

df = pd.DataFrame(np.random.randn(6,4), index=dates, columns=list('ABCD'))
df
DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04',
               '2013-01-05', '2013-01-06'],
              dtype='datetime64[ns]', freq='D')
Out[6]:
A B C D
2013-01-01 -0.732780 -1.518687 0.071112 0.850960
2013-01-02 0.814817 1.000090 0.473998 0.888938
2013-01-03 1.942117 2.366381 -0.347881 0.450864
2013-01-04 -0.606737 -0.991934 -0.795501 0.870672
2013-01-05 0.901766 -2.518833 -0.361622 -0.183943
2013-01-06 -1.186593 -0.679354 -0.215738 -0.986723

On peut également créer un DataFrame en passant un dictionnaire d'objets qui peut être converti en sorte de série.

In [8]:
df2 = pd.DataFrame({ 'A' : 1.,
'B' : pd.Timestamp('20130102'),
'C' : pd.Series(1,index=list(range(4)),dtype='float32'),
'D' : np.array([3] * 4,dtype='int32'),
'E' : pd.Categorical(["test","train","test","train"]),
'F' : 'foo' })

df2
Out[8]:
A B C D E F
0 1.0 2013-01-02 1.0 3 test foo
1 1.0 2013-01-02 1.0 3 train foo
2 1.0 2013-01-02 1.0 3 test foo
3 1.0 2013-01-02 1.0 3 train foo

Chaque colonne a son propre dtypes

In [10]:
df2.dtypes
Out[10]:
A           float64
B    datetime64[ns]
C           float32
D             int32
E          category
F            object
dtype: object

On peut afficher les premières lignes et les dernières

In [11]:
print(df.head())
print(df.tail())
                   A         B         C         D
2013-01-01 -0.732780 -1.518687  0.071112  0.850960
2013-01-02  0.814817  1.000090  0.473998  0.888938
2013-01-03  1.942117  2.366381 -0.347881  0.450864
2013-01-04 -0.606737 -0.991934 -0.795501  0.870672
2013-01-05  0.901766 -2.518833 -0.361622 -0.183943
                   A         B         C         D
2013-01-02  0.814817  1.000090  0.473998  0.888938
2013-01-03  1.942117  2.366381 -0.347881  0.450864
2013-01-04 -0.606737 -0.991934 -0.795501  0.870672
2013-01-05  0.901766 -2.518833 -0.361622 -0.183943
2013-01-06 -1.186593 -0.679354 -0.215738 -0.986723

On peut afficher l'index, les colonnes et les données numpy

In [12]:
print(df.index)
print(df.columns)
print(df.values)
DatetimeIndex(['2013-01-01', '2013-01-02', '2013-01-03', '2013-01-04',
               '2013-01-05', '2013-01-06'],
              dtype='datetime64[ns]', freq='D')
Index(['A', 'B', 'C', 'D'], dtype='object')
[[-0.7327803  -1.5186872   0.07111171  0.85096046]
 [ 0.81481699  1.00008989  0.47399833  0.88893835]
 [ 1.94211709  2.36638081 -0.34788144  0.45086436]
 [-0.60673653 -0.99193354 -0.79550129  0.87067205]
 [ 0.9017658  -2.5188326  -0.36162222 -0.1839427 ]
 [-1.18659285 -0.67935373 -0.21573757 -0.98672254]]

La méthode describe permet d'afficher un résumé des données

In [15]:
df.describe()
Out[15]:
A B C D
count 6.000000 6.000000 6.000000 6.000000
mean 0.188765 -0.390389 -0.195939 0.315128
std 1.212290 1.776620 0.431402 0.759910
min -1.186593 -2.518833 -0.795501 -0.986723
25% -0.701269 -1.386999 -0.358187 -0.025241
50% 0.104040 -0.835644 -0.281810 0.650912
75% 0.880029 0.580229 -0.000601 0.865744
max 1.942117 2.366381 0.473998 0.888938

On peut faire la transposée, trier en fonction d'un axe ou des valeurs

In [16]:
print(df.T)
   2013-01-01  2013-01-02  2013-01-03  2013-01-04  2013-01-05  2013-01-06
A   -0.732780    0.814817    1.942117   -0.606737    0.901766   -1.186593
B   -1.518687    1.000090    2.366381   -0.991934   -2.518833   -0.679354
C    0.071112    0.473998   -0.347881   -0.795501   -0.361622   -0.215738
D    0.850960    0.888938    0.450864    0.870672   -0.183943   -0.986723
In [18]:
df.sort_index(axis=1, ascending=False)
Out[18]:
D C B A
2013-01-01 0.850960 0.071112 -1.518687 -0.732780
2013-01-02 0.888938 0.473998 1.000090 0.814817
2013-01-03 0.450864 -0.347881 2.366381 1.942117
2013-01-04 0.870672 -0.795501 -0.991934 -0.606737
2013-01-05 -0.183943 -0.361622 -2.518833 0.901766
2013-01-06 -0.986723 -0.215738 -0.679354 -1.186593
In [19]:
df.sort_values(by='B')
Out[19]:
A B C D
2013-01-05 0.901766 -2.518833 -0.361622 -0.183943
2013-01-01 -0.732780 -1.518687 0.071112 0.850960
2013-01-04 -0.606737 -0.991934 -0.795501 0.870672
2013-01-06 -1.186593 -0.679354 -0.215738 -0.986723
2013-01-02 0.814817 1.000090 0.473998 0.888938
2013-01-03 1.942117 2.366381 -0.347881 0.450864

Selection des données

Getting

Selection d'une colonne (équivalent à df.A)

In [21]:
print(df['A'])
print(df[0:3])
print(df['20130102':'20130104'])
2013-01-01   -0.732780
2013-01-02    0.814817
2013-01-03    1.942117
2013-01-04   -0.606737
2013-01-05    0.901766
2013-01-06   -1.186593
Freq: D, Name: A, dtype: float64
                   A         B         C         D
2013-01-01 -0.732780 -1.518687  0.071112  0.850960
2013-01-02  0.814817  1.000090  0.473998  0.888938
2013-01-03  1.942117  2.366381 -0.347881  0.450864
                   A         B         C         D
2013-01-02  0.814817  1.000090  0.473998  0.888938
2013-01-03  1.942117  2.366381 -0.347881  0.450864
2013-01-04 -0.606737 -0.991934 -0.795501  0.870672

Selection par Label

En utilisant un label

In [22]:
df.loc[dates[0]]
Out[22]:
A   -0.732780
B   -1.518687
C    0.071112
D    0.850960
Name: 2013-01-01 00:00:00, dtype: float64

Selection de plusieurs axes par label

In [23]:
df.loc[:,['A','B']]
Out[23]:
A B
2013-01-01 -0.732780 -1.518687
2013-01-02 0.814817 1.000090
2013-01-03 1.942117 2.366381
2013-01-04 -0.606737 -0.991934
2013-01-05 0.901766 -2.518833
2013-01-06 -1.186593 -0.679354

Avec le label slicing, les deux points de terminaisons sont INCLUS

In [24]:
df.loc['20130102':'20130104', ['A','B']]
Out[24]:
A B
2013-01-02 0.814817 1.000090
2013-01-03 1.942117 2.366381
2013-01-04 -0.606737 -0.991934

Obtenir une valeur scalaire

In [27]:
df.loc[dates[0],'A']
Out[27]:
-0.73278030104433001

Acces plus rapide (méthode équivalente à la précédente)

In [28]:
df.at[dates[0],'A']
Out[28]:
-0.73278030104433001

Selection par position

Integer :

In [29]:
df.iloc[3]
Out[29]:
A   -0.606737
B   -0.991934
C   -0.795501
D    0.870672
Name: 2013-01-04 00:00:00, dtype: float64

Tranches d'entiers, similaire à numpy

In [32]:
df.iloc[3:5,0:2]
Out[32]:
A B
2013-01-04 -0.606737 -0.991934
2013-01-05 0.901766 -2.518833

Par liste d'entiers

In [33]:
df.iloc[[1,2,4],[0,2]]
Out[33]:
A C
2013-01-02 0.814817 0.473998
2013-01-03 1.942117 -0.347881
2013-01-05 0.901766 -0.361622

Découpage de ligne explicite

In [34]:
df.iloc[1:3,:]
Out[34]:
A B C D
2013-01-02 0.814817 1.000090 0.473998 0.888938
2013-01-03 1.942117 2.366381 -0.347881 0.450864

Obtenir une valeur explicitement

In [35]:
df.iloc[1,1]
Out[35]:
1.0000898939031513

Acces rapide au scalaire

In [36]:
df.iat[1,1]
Out[36]:
1.0000898939031513

Indexation booléenne

En utilisant une valeur sur une colonne :

In [37]:
df[df.A > 0]
Out[37]:
A B C D
2013-01-02 0.814817 1.000090 0.473998 0.888938
2013-01-03 1.942117 2.366381 -0.347881 0.450864
2013-01-05 0.901766 -2.518833 -0.361622 -0.183943

Opérateur where :

In [38]:
df[df > 0]
Out[38]:
A B C D
2013-01-01 NaN NaN 0.071112 0.850960
2013-01-02 0.814817 1.000090 0.473998 0.888938
2013-01-03 1.942117 2.366381 NaN 0.450864
2013-01-04 NaN NaN NaN 0.870672
2013-01-05 0.901766 NaN NaN NaN
2013-01-06 NaN NaN NaN NaN

Pour filter, on utilise la méthode isin()

In [41]:
df2 = df.copy()
df2['E'] = ['one', 'one','two','three','four','three']
print(df2)

df2[df2['E'].isin(['two','four'])]
                   A         B         C         D      E
2013-01-01 -0.732780 -1.518687  0.071112  0.850960    one
2013-01-02  0.814817  1.000090  0.473998  0.888938    one
2013-01-03  1.942117  2.366381 -0.347881  0.450864    two
2013-01-04 -0.606737 -0.991934 -0.795501  0.870672  three
2013-01-05  0.901766 -2.518833 -0.361622 -0.183943   four
2013-01-06 -1.186593 -0.679354 -0.215738 -0.986723  three
Out[41]:
A B C D E
2013-01-03 1.942117 2.366381 -0.347881 0.450864 two
2013-01-05 0.901766 -2.518833 -0.361622 -0.183943 four

Ajouter / modifier valeurs / colonnes

Ajouter une nouvelle colonne automatiquement aligne les données par index.

In [45]:
s1 = pd.Series([1,2,3,4,5,6], index=pd.date_range('20130102', periods=6))
print(s1)
df['F'] = s1
2013-01-02    1
2013-01-03    2
2013-01-04    3
2013-01-05    4
2013-01-06    5
2013-01-07    6
Freq: D, dtype: int64

Modifier une valeur par label

In [48]:
df.at[dates[0],'A'] = 0
In [47]:
df.loc[dates[0],'A']
Out[47]:
0.0

Modifier une valeur par position

In [49]:
df.iat[0,1] = 0

Modifier une valeur en assignant un tableau numpy

In [51]:
df.loc[:,'D'] = np.array([5] * len(df))
In [52]:
df
Out[52]:
A B C D F
2013-01-01 0.000000 0.000000 0.071112 5 NaN
2013-01-02 0.814817 1.000090 0.473998 5 1.0
2013-01-03 1.942117 2.366381 -0.347881 5 2.0
2013-01-04 -0.606737 -0.991934 -0.795501 5 3.0
2013-01-05 0.901766 -2.518833 -0.361622 5 4.0
2013-01-06 -1.186593 -0.679354 -0.215738 5 5.0

Gérer les données manquantes

Pandas utilise le type np.nan pour représenter les valeurs manquantes. Ce n'est pas codé pour faire des calculs.

Reindex permet de changer/ajouter/supprimer les index d'un axe. Cette fonction retourne une copie des données

In [56]:
df1 = df.reindex(index=dates[0:4], columns=list(df.columns) + ['E'])
df1.loc[dates[0]:dates[1],'E'] = 1
df1
Out[56]:
A B C D F E
2013-01-01 0.000000 0.000000 0.071112 5 NaN 1.0
2013-01-02 0.814817 1.000090 0.473998 5 1.0 1.0
2013-01-03 1.942117 2.366381 -0.347881 5 2.0 NaN
2013-01-04 -0.606737 -0.991934 -0.795501 5 3.0 NaN

Pour supprimer les lignes contenant des NaN :

In [57]:
df1.dropna(how='any')
Out[57]:
A B C D F E
2013-01-02 0.814817 1.00009 0.473998 5 1.0 1.0

Remplacement des valeurs manquantes

In [60]:
df1.fillna(value=5)
Out[60]:
A B C D F E
2013-01-01 0.000000 0.000000 0.071112 5 5.0 1.0
2013-01-02 0.814817 1.000090 0.473998 5 1.0 1.0
2013-01-03 1.942117 2.366381 -0.347881 5 2.0 5.0
2013-01-04 -0.606737 -0.991934 -0.795501 5 3.0 5.0

Obtenir le masque de booléen de l'emplacement des nan

In [61]:
pd.isnull(df1)
Out[61]:
A B C D F E
2013-01-01 False False False False True False
2013-01-02 False False False False False False
2013-01-03 False False False False False True
2013-01-04 False False False False False True

Opérations

Stats

Les opérations excluent généralement les données manquantes.

In [65]:
print(df.mean())
print(df.mean(1)) #Autre axe
A    0.310895
B   -0.137275
C   -0.195939
D    5.000000
F    3.000000
dtype: float64
2013-01-01    1.267778
2013-01-02    1.657781
2013-01-03    2.192123
2013-01-04    1.121166
2013-01-05    1.404262
2013-01-06    1.583663
Freq: D, dtype: float64

Situation avec des objets de dimmension différentes. En plus, pandas va automatiquement étendre la donnée sur la dimension spécifiée

In [67]:
s = pd.Series([1,3,5,np.nan,6,8], index=dates).shift(2)

print(s)

df.sub(s, axis='index')
2013-01-01    NaN
2013-01-02    NaN
2013-01-03    1.0
2013-01-04    3.0
2013-01-05    5.0
2013-01-06    NaN
Freq: D, dtype: float64
Out[67]:
A B C D F
2013-01-01 NaN NaN NaN NaN NaN
2013-01-02 NaN NaN NaN NaN NaN
2013-01-03 0.942117 1.366381 -1.347881 4.0 1.0
2013-01-04 -3.606737 -3.991934 -3.795501 2.0 0.0
2013-01-05 -4.098234 -7.518833 -5.361622 0.0 -1.0
2013-01-06 NaN NaN NaN NaN NaN
In [69]:
help(df.sub)
Help on method sub in module pandas.core.ops:

sub(other, axis='columns', level=None, fill_value=None) method of pandas.core.frame.DataFrame instance
    Subtraction of dataframe and other, element-wise (binary operator `sub`).
    
    Equivalent to ``dataframe - other``, but with support to substitute a fill_value for
    missing data in one of the inputs.
    
    Parameters
    ----------
    other : Series, DataFrame, or constant
    axis : {0, 1, 'index', 'columns'}
        For Series input, axis to match Series index on
    fill_value : None or float value, default None
        Fill missing (NaN) values with this value. If both DataFrame
        locations are missing, the result will be missing
    level : int or name
        Broadcast across a level, matching Index values on the
        passed MultiIndex level
    
    Notes
    -----
    Mismatched indices will be unioned together
    
    Returns
    -------
    result : DataFrame
    
    See also
    --------
    DataFrame.rsub

In [70]:
df.sub(s, axis='index')
Out[70]:
A B C D F
2013-01-01 NaN NaN NaN NaN NaN
2013-01-02 NaN NaN NaN NaN NaN
2013-01-03 0.942117 1.366381 -1.347881 4.0 1.0
2013-01-04 -3.606737 -3.991934 -3.795501 2.0 0.0
2013-01-05 -4.098234 -7.518833 -5.361622 0.0 -1.0
2013-01-06 NaN NaN NaN NaN NaN

Apply

Appliquer des foncitons aux données

In [71]:
df.apply(np.cumsum)
Out[71]:
A B C D F
2013-01-01 0.000000 0.000000 0.071112 5 NaN
2013-01-02 0.814817 1.000090 0.545110 10 1.0
2013-01-03 2.756934 3.366471 0.197229 15 3.0
2013-01-04 2.150198 2.374537 -0.598273 20 6.0
2013-01-05 3.051963 -0.144295 -0.959895 25 10.0
2013-01-06 1.865370 -0.823649 -1.175632 30 15.0
In [80]:
df.apply((lambda x: x.max() - x.min()))
Out[80]:
A    3.128710
B    4.885213
C    1.269500
D    0.000000
F    4.000000
dtype: float64

Histogramme

In [82]:
s = pd.Series(np.random.randint(0, 7, size=10))
print(s)
print(s.value_counts())
0    0
1    6
2    5
3    6
4    2
5    0
6    2
7    5
8    2
9    5
dtype: int32
5    3
2    3
6    2
0    2
dtype: int64

Methodes String

Les séries sont équipées de méthodes pour traiter les strings avec l'attribut str qui rend facile la manipulation de chaque élémen d'un tableau. On utilise régulièrement des expressions régulières.

In [84]:
s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'])
s.str.lower()
Out[84]:
0       a
1       b
2       c
3    aaba
4    baca
5     NaN
6    caba
7     dog
8     cat
dtype: object

Regrouper

Concaténation

Pandas fournit des methodes pour facilement combiner des Series, DatFrame et des Panel objets avec des types variés de set logique pour les indexes et des fonctionnalités d'algèbre dans le cas de jointure / regroupement

On peut concaténer des objets pandas avec concat()

In [89]:
df = pd.DataFrame(np.random.randn(10, 4))
print(df)
          0         1         2         3
0  1.171293 -0.161866 -1.644625 -0.008880
1 -0.405934  0.287936 -0.557463  0.574615
2 -0.765230  0.904218  1.066919  0.136649
3 -0.587428  1.144002  1.014680 -2.829022
4 -0.967095  0.435631 -0.468233 -0.454541
5  0.678089 -0.917802 -0.440931 -0.148592
6  0.063590  1.201781  0.404632 -0.336294
7 -0.583866 -1.388321  0.863296 -0.127306
8  0.082388  0.721320  0.408385 -0.960828
9  0.018883 -0.722289  1.156993 -1.733633
In [90]:
pieces = [df[:3], df[3:7], df[7:]]
In [88]:
pd.concat(pieces)
Out[88]:
0 1 2 3
0 0.903259 1.540057 0.883655 1.226059
1 0.105811 1.002626 -0.979134 -1.721460
2 1.427238 -0.610344 0.291117 0.316185
3 -1.392566 1.668483 1.616147 -2.660953
4 -0.007495 0.781826 0.693082 -1.419591
5 2.366686 0.510887 -1.974254 0.124177
6 -2.054149 -0.868643 -0.037250 -0.516805
7 0.635638 -1.054890 -0.272888 -1.481083
8 -2.238861 0.197462 -0.089813 -0.112026
9 0.559386 -0.414531 0.497154 0.823401

Jointures

On peut merger à la manière de requete SQL.

In [92]:
left = pd.DataFrame({'key': ['foo', 'foo'], 'lval': [1, 2]})
right = pd.DataFrame({'key': ['foo', 'foo'], 'rval': [4, 5]})

print(left)
print(right)

print(pd.merge(left, right, on='key'))
   key  lval
0  foo     1
1  foo     2
   key  rval
0  foo     4
1  foo     5
   key  lval  rval
0  foo     1     4
1  foo     1     5
2  foo     2     4
3  foo     2     5

Append

In [95]:
df = pd.DataFrame(np.random.randn(8, 4), columns=['A','B','C','D'])
df
Out[95]:
A B C D
0 0.550062 1.032200 0.523171 0.996602
1 -0.733652 -0.189665 -1.321626 -0.772544
2 -0.541688 0.753546 1.113926 0.778031
3 -1.679464 1.598649 0.259774 0.537607
4 -1.509523 0.416387 -0.086938 0.345902
5 1.002424 0.203647 1.222880 -0.912706
6 0.272985 0.325784 -0.706740 1.579292
7 -0.287682 0.724048 -1.296466 -0.513019
In [96]:
s = df.iloc[3]
df.append(s, ignore_index=True)
Out[96]:
A B C D
0 0.550062 1.032200 0.523171 0.996602
1 -0.733652 -0.189665 -1.321626 -0.772544
2 -0.541688 0.753546 1.113926 0.778031
3 -1.679464 1.598649 0.259774 0.537607
4 -1.509523 0.416387 -0.086938 0.345902
5 1.002424 0.203647 1.222880 -0.912706
6 0.272985 0.325784 -0.706740 1.579292
7 -0.287682 0.724048 -1.296466 -0.513019
8 -1.679464 1.598649 0.259774 0.537607

Groupement

Group by inclue les étapes suivantes :

  • Séparation de la donnée en groupes
  • Appliquer une fonction a chaque group indépendamment
  • Combiner les resultats dans une structure de données
In [97]:
df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
'foo', 'bar', 'foo', 'foo'],
'B' : ['one', 'one', 'two', 'three',
'two', 'two', 'one', 'three'],
'C' : np.random.randn(8),
'D' : np.random.randn(8)})
In [98]:
df
Out[98]:
A B C D
0 foo one 0.348822 -1.146204
1 bar one 0.733202 -0.355111
2 foo two 0.837183 1.079539
3 bar three 0.206230 -1.330003
4 foo two 1.538722 -0.019436
5 bar two -0.335398 -0.307642
6 foo one -0.971759 -0.207558
7 foo three 0.114024 1.209354

Groupement et somme des groupes

In [99]:
df.groupby('A').sum()
Out[99]:
C D
A
bar 0.604034 -1.992756
foo 1.866993 0.915695

Groupement de multiple colonnes

In [100]:
df.groupby(['A','B']).sum()
Out[100]:
C D
A B
bar one 0.733202 -0.355111
three 0.206230 -1.330003
two -0.335398 -0.307642
foo one -0.622936 -1.353762
three 0.114024 1.209354
two 2.375905 1.060103

Reformation

Stack

In [101]:
tuples = list(zip(*[['bar', 'bar', 'baz', 'baz',
'foo', 'foo', 'qux', 'qux'],
['one', 'two', 'one', 'two',
'one', 'two', 'one', 'two']]))

index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
df = pd.DataFrame(np.random.randn(8, 2), index=index, columns=['A', 'B'])
df2 = df[:4]
df2
Out[101]:
A B
first second
bar one 2.299421 1.823510
two -0.013680 0.406099
baz one 1.299891 0.184349
two -1.363738 1.320455

La méthode stack() compresses un level dans les colonnes du dataframe

In [103]:
stacked = df2.stack()
stacked
Out[103]:
first  second   
bar    one     A    2.299421
               B    1.823510
       two     A   -0.013680
               B    0.406099
baz    one     A    1.299891
               B    0.184349
       two     A   -1.363738
               B    1.320455
dtype: float64

Avec une 'stacked' dataframe ou série, l'opération inverse est unstack()

In [ ]: