Cube de données et pandas¶
[1]:
%matplotlib inline
Un cube¶
Les données sont disposées sous forme de table, il y a N colonnes de coordonnées et une colonne numérique. Avec N=4
, cela équivaut à une fonction .
[3]:
import pandas
data = [
{"X1": "A1", "X2": "B1", "X3": "C1", "X4": "D1", "Y": 3},
{"X1": "A1", "X2": "B1", "X3": "C1", "X4": "D2", "Y": 4},
{"X1": "A1", "X2": "B1", "X3": "C2", "X4": "D1", "Y": 5},
{"X1": "A1", "X2": "B1", "X3": "C2", "X4": "D2", "Y": 6},
{"X1": "A1", "X2": "B2", "X3": "C1", "X4": "D1", "Y": 7},
{"X1": "A1", "X2": "B2", "X3": "C1", "X4": "D2", "Y": 8},
{"X1": "A1", "X2": "B2", "X3": "C2", "X4": "D1", "Y": 9},
{"X1": "A1", "X2": "B2", "X3": "C2", "X4": "D2", "Y": 2},
{"X1": "A2", "X2": "B2", "X3": "C2", "X4": "D2", "Y": 1},
]
df = pandas.DataFrame(data)
df
[3]:
X1 | X2 | X3 | X4 | Y | |
---|---|---|---|---|---|
0 | A1 | B1 | C1 | D1 | 3 |
1 | A1 | B1 | C1 | D2 | 4 |
2 | A1 | B1 | C2 | D1 | 5 |
3 | A1 | B1 | C2 | D2 | 6 |
4 | A1 | B2 | C1 | D1 | 7 |
5 | A1 | B2 | C1 | D2 | 8 |
6 | A1 | B2 | C2 | D1 | 9 |
7 | A1 | B2 | C2 | D2 | 2 |
8 | A2 | B2 | C2 | D2 | 1 |
Pivot¶
Le pivot consiste à choisir des colonnes pour les indices des lignes, et d’autres pour les colonnes. Le pivot fonctionne si chaque valeur numérique peut être identifiée de manière unique.
[4]:
df.pivot(index=["X1", "X2"], columns=["X3", "X4"], values="Y")
[4]:
X3 | C1 | C2 | |||
---|---|---|---|---|---|
X4 | D1 | D2 | D1 | D2 | |
X1 | X2 | ||||
A1 | B1 | 3.0 | 4.0 | 5.0 | 6.0 |
B2 | 7.0 | 8.0 | 9.0 | 2.0 | |
A2 | B2 | NaN | NaN | NaN | 1.0 |
[5]:
df.pivot(index=["X1"], columns=["X2", "X3", "X4"], values="Y")
[5]:
X2 | B1 | B2 | ||||||
---|---|---|---|---|---|---|---|---|
X3 | C1 | C2 | C1 | C2 | ||||
X4 | D1 | D2 | D1 | D2 | D1 | D2 | D1 | D2 |
X1 | ||||||||
A1 | 3.0 | 4.0 | 5.0 | 6.0 | 7.0 | 8.0 | 9.0 | 2.0 |
A2 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | 1.0 |
Index et MultiIndex¶
A quoi correspondent les index ?
[6]:
piv = df.pivot(index=["X1"], columns=["X2", "X3", "X4"], values="Y")
piv.columns
[6]:
MultiIndex([('B1', 'C1', 'D1'),
('B1', 'C1', 'D2'),
('B1', 'C2', 'D1'),
('B1', 'C2', 'D2'),
('B2', 'C1', 'D1'),
('B2', 'C1', 'D2'),
('B2', 'C2', 'D1'),
('B2', 'C2', 'D2')],
names=['X2', 'X3', 'X4'])
[12]:
piv.index
[12]:
Index(['A1', 'A2'], dtype='object', name='X1')
[14]:
piv = df.pivot(index=["X1", "X2"], columns=["X3", "X4"], values="Y")
piv.columns
[14]:
MultiIndex([('C1', 'D1'),
('C1', 'D2'),
('C2', 'D1'),
('C2', 'D2')],
names=['X3', 'X4'])
[15]:
piv.index
[15]:
MultiIndex([('A1', 'B1'),
('A1', 'B2'),
('A2', 'B2')],
names=['X1', 'X2'])
Les Index indexent les valeurs et sont équivalents à des colonnes, les MultiIndex indexent les valeurs et sont équivalents à plusieurs colonnes. On récupère le nom des colonnes avec la propriété names.
[16]:
piv.columns.names
[16]:
FrozenList(['X3', 'X4'])
[17]:
piv.index.names
[17]:
FrozenList(['X1', 'X2'])
On récupère le nombre de colonnes avec nlevels.
[20]:
piv.columns.nlevels, piv.index.nlevels
[20]:
(2, 2)
Ou encore levels.
[19]:
piv.columns.levels, piv.index.levels
[19]:
(FrozenList([['C1', 'C2'], ['D1', 'D2']]),
FrozenList([['A1', 'A2'], ['B1', 'B2']]))
On veut accéder à un élément.
[21]:
piv.loc["A1"]
[21]:
X3 | C1 | C2 | ||
---|---|---|---|---|
X4 | D1 | D2 | D1 | D2 |
X2 | ||||
B1 | 3.0 | 4.0 | 5.0 | 6.0 |
B2 | 7.0 | 8.0 | 9.0 | 2.0 |
[24]:
piv.loc["A1", "B1"]
[24]:
X3 X4
C1 D1 3.0
D2 4.0
C2 D1 5.0
D2 6.0
Name: (A1, B1), dtype: float64
[25]:
piv["C1"]
[25]:
X4 | D1 | D2 | |
---|---|---|---|
X1 | X2 | ||
A1 | B1 | 3.0 | 4.0 |
B2 | 7.0 | 8.0 | |
A2 | B2 | NaN | NaN |
[26]:
piv["C1", "D1"]
[26]:
X1 X2
A1 B1 3.0
B2 7.0
A2 B2 NaN
Name: (C1, D1), dtype: float64
Passer de l’un à l’autre¶
Peut-on retrouver les données originales à partir du pivot?
[27]:
piv
[27]:
X3 | C1 | C2 | |||
---|---|---|---|---|---|
X4 | D1 | D2 | D1 | D2 | |
X1 | X2 | ||||
A1 | B1 | 3.0 | 4.0 | 5.0 | 6.0 |
B2 | 7.0 | 8.0 | 9.0 | 2.0 | |
A2 | B2 | NaN | NaN | NaN | 1.0 |
Ce qui ne marche pas reset_index.
[28]:
piv.reset_index(drop=False)
[28]:
X3 | X1 | X2 | C1 | C2 | ||
---|---|---|---|---|---|---|
X4 | D1 | D2 | D1 | D2 | ||
0 | A1 | B1 | 3.0 | 4.0 | 5.0 | 6.0 |
1 | A1 | B2 | 7.0 | 8.0 | 9.0 | 2.0 |
2 | A2 | B2 | NaN | NaN | NaN | 1.0 |
Ce qui marche…
stack : fait passer une coordonnée des colonnes aux lignes
unstack : fait passer une coordonnée des lignes aux colonnes
[31]:
piv.stack(0, future_stack=True)
[31]:
X4 | D1 | D2 | ||
---|---|---|---|---|
X1 | X2 | X3 | ||
A1 | B1 | C1 | 3.0 | 4.0 |
C2 | 5.0 | 6.0 | ||
B2 | C1 | 7.0 | 8.0 | |
C2 | 9.0 | 2.0 | ||
A2 | B2 | C1 | NaN | NaN |
C2 | NaN | 1.0 |
[32]:
piv.stack([0, 1], future_stack=True)
[32]:
X1 X2 X3 X4
A1 B1 C1 D1 3.0
D2 4.0
C2 D1 5.0
D2 6.0
B2 C1 D1 7.0
D2 8.0
C2 D1 9.0
D2 2.0
A2 B2 C1 D1 NaN
D2 NaN
C2 D1 NaN
D2 1.0
dtype: float64
[33]:
piv.stack("X4", future_stack=True)
[33]:
X3 | C1 | C2 | ||
---|---|---|---|---|
X1 | X2 | X4 | ||
A1 | B1 | D1 | 3.0 | 5.0 |
D2 | 4.0 | 6.0 | ||
B2 | D1 | 7.0 | 9.0 | |
D2 | 8.0 | 2.0 | ||
A2 | B2 | D1 | NaN | NaN |
D2 | NaN | 1.0 |
[34]:
piv.unstack("X1")
[34]:
X3 | C1 | C2 | ||||||
---|---|---|---|---|---|---|---|---|
X4 | D1 | D2 | D1 | D2 | ||||
X1 | A1 | A2 | A1 | A2 | A1 | A2 | A1 | A2 |
X2 | ||||||||
B1 | 3.0 | NaN | 4.0 | NaN | 5.0 | NaN | 6.0 | NaN |
B2 | 7.0 | NaN | 8.0 | NaN | 9.0 | NaN | 2.0 | 1.0 |
On peut changer l’ordre des index.
[41]:
view = piv.unstack("X1")
new_index = view.columns.reorder_levels(["X1", "X3", "X4"])
new_index
[41]:
MultiIndex([('A1', 'C1', 'D1'),
('A2', 'C1', 'D1'),
('A1', 'C1', 'D2'),
('A2', 'C1', 'D2'),
('A1', 'C2', 'D1'),
('A2', 'C2', 'D1'),
('A1', 'C2', 'D2'),
('A2', 'C2', 'D2')],
names=['X1', 'X3', 'X4'])
[43]:
view.columns = new_index
view
[43]:
X1 | A1 | A2 | A1 | A2 | A1 | A2 | A1 | A2 |
---|---|---|---|---|---|---|---|---|
X3 | C1 | C1 | C1 | C1 | C2 | C2 | C2 | C2 |
X4 | D1 | D1 | D2 | D2 | D1 | D1 | D2 | D2 |
X2 | ||||||||
B1 | 3.0 | NaN | 4.0 | NaN | 5.0 | NaN | 6.0 | NaN |
B2 | 7.0 | NaN | 8.0 | NaN | 9.0 | NaN | 2.0 | 1.0 |
Un pivot aggrégé¶
La fonction pivot suppose que la transformation conserve chaque valeur sans les aggréger ce qui permet de restaurer les données sous leurs forme initiale. Mais ce n’est pas toujours ce qu’on souhaite faire.
[44]:
df
[44]:
X1 | X2 | X3 | X4 | Y | |
---|---|---|---|---|---|
0 | A1 | B1 | C1 | D1 | 3 |
1 | A1 | B1 | C1 | D2 | 4 |
2 | A1 | B1 | C2 | D1 | 5 |
3 | A1 | B1 | C2 | D2 | 6 |
4 | A1 | B2 | C1 | D1 | 7 |
5 | A1 | B2 | C1 | D2 | 8 |
6 | A1 | B2 | C2 | D1 | 9 |
7 | A1 | B2 | C2 | D2 | 2 |
8 | A2 | B2 | C2 | D2 | 1 |
[46]:
try:
df.pivot(index=["X2"], columns=["X3", "X4"], values="Y")
except Exception as e:
print("Le pivot ne conserve pas les données.")
print(e)
Le pivot ne conserve pas les données.
Index contains duplicate entries, cannot reshape
On utlise alors pivot_table.
[48]:
df.pivot_table(index=["X2"], columns=["X3", "X4"], values="Y", aggfunc="count")
[48]:
X3 | C1 | C2 | ||
---|---|---|---|---|
X4 | D1 | D2 | D1 | D2 |
X2 | ||||
B1 | 1 | 1 | 1 | 1 |
B2 | 1 | 1 | 1 | 2 |
[49]:
df.pivot_table(index=["X2"], columns=["X3", "X4"], values="Y", aggfunc="sum")
[49]:
X3 | C1 | C2 | ||
---|---|---|---|---|
X4 | D1 | D2 | D1 | D2 |
X2 | ||||
B1 | 3 | 4 | 5 | 6 |
B2 | 7 | 8 | 9 | 3 |
XArray¶
Le package XArray représente des cubes de données de façon plus efficace mais parfois moins intuitive.
[64]:
import xarray as xr
cube = xr.Dataset.from_dataframe(df.set_index(["X1", "X2", "X3", "X4"]))
cube
[64]:
<xarray.Dataset> Size: 192B Dimensions: (X1: 2, X2: 2, X3: 2, X4: 2) Coordinates: * X1 (X1) object 16B 'A1' 'A2' * X2 (X2) object 16B 'B1' 'B2' * X3 (X3) object 16B 'C1' 'C2' * X4 (X4) object 16B 'D1' 'D2' Data variables: Y (X1, X2, X3, X4) float64 128B 3.0 4.0 5.0 6.0 ... nan nan nan 1.0
Modifier une valeur existante.
[65]:
cube["Y"].loc[{"X1": "A1", "X2": "B2", "X3": "C2", "X4": "D1"}] = 100
cube
[65]:
<xarray.Dataset> Size: 192B Dimensions: (X1: 2, X2: 2, X3: 2, X4: 2) Coordinates: * X1 (X1) object 16B 'A1' 'A2' * X2 (X2) object 16B 'B1' 'B2' * X3 (X3) object 16B 'C1' 'C2' * X4 (X4) object 16B 'D1' 'D2' Data variables: Y (X1, X2, X3, X4) float64 128B 3.0 4.0 5.0 6.0 ... nan nan nan 1.0
[66]:
cube.mean(dim=("X2", "X3"))
[66]:
<xarray.Dataset> Size: 64B Dimensions: (X1: 2, X4: 2) Coordinates: * X1 (X1) object 16B 'A1' 'A2' * X4 (X4) object 16B 'D1' 'D2' Data variables: Y (X1, X4) float64 32B 28.75 5.0 nan 1.0
[67]:
cube.to_dataframe()
[67]:
Y | ||||
---|---|---|---|---|
X1 | X2 | X3 | X4 | |
A1 | B1 | C1 | D1 | 3.0 |
D2 | 4.0 | |||
C2 | D1 | 5.0 | ||
D2 | 6.0 | |||
B2 | C1 | D1 | 7.0 | |
D2 | 8.0 | |||
C2 | D1 | 100.0 | ||
D2 | 2.0 | |||
A2 | B1 | C1 | D1 | NaN |
D2 | NaN | |||
C2 | D1 | NaN | ||
D2 | NaN | |||
B2 | C1 | D1 | NaN | |
D2 | NaN | |||
C2 | D1 | NaN | ||
D2 | 1.0 |
[ ]: