Pourquoi attribuer avec [:] versus iloc [:] donne des résultats différents chez les pandas?

13

Je suis tellement confus avec différentes méthodes d'indexation utilisées ilocdans les pandas.

Disons que j'essaie de convertir un Dataframe 1-d en un Dataframe 2-d. J'ai d'abord le Dataframe 1-d suivant

a_array = [1,2,3,4,5,6,7,8]
a_df = pd.DataFrame(a_array).T

Et je vais convertir cela en un Dataframe 2D avec la taille de 2x4. Je commence par prérégler le Dataframe 2D comme suit:

b_df = pd.DataFrame(columns=range(4),index=range(2))

Ensuite, j'utilise for-loop pour m'aider à convertir a_df(1-d) en b_df(2-d) avec le code suivant

for i in range(2):
    b_df.iloc[i,:] = a_df.iloc[0,i*4:(i+1)*4]

Cela ne me donne que les résultats suivants

     0    1    2    3
0    1    2    3    4
1  NaN  NaN  NaN  NaN

Mais quand j'ai changé b_df.iloc[i,:]pour b_df.iloc[i][:]. Le résultat est correct comme le suivant, c'est ce que je veux

   0  1  2  3
0  1  2  3  4
1  5  6  7  8

Quelqu'un pourrait-il m'expliquer quelle est la différence entre .iloc[i,:]et .iloc[i][:]est, et pourquoi a .iloc[i][:]fonctionné dans mon exemple ci-dessus mais pas.iloc[i,:]

Tommy Yip
la source
C'est curieux. b_df.iloc[1] = a_df.iloc[0, 4:8]affecte une série avec index [4, 5, 6, 7]à une série avec index [0, 1, 2, 3]. Il n'y a pas de chevauchement donc NaNs est assigné à tous les éléments. Jusqu'à présent, cela a du sens pour moi. Mais comme vous, je ne sais pas pourquoi b_df.iloc[1][:] = ...se comporte différemment - inspecter les objets b_df.iloc[1]et b_df.iloc[1][:]ne révèle aucune différence entre les indices. Ma meilleure supposition serait que l'attribution directe à une copie ( [:]) est traitée comme un cas spécial par Pandas, ce qui lui fait ignorer l'index du cessionnaire et créer cette différence.
Seb
Je pense que c'est à cause de l'indice et du succès de la première ligne car il a le même indice
Phung Duy Phong
1
La chose clé à retenir à propos des pandas est que la plupart de toutes les opérations dans les pandas utilisent un concept appelé «alignement des données instrinic». Cela signifie que presque toutes les opérations que vous effectuez avec des pandas aligneront les index des deux côtés de la déclaration. Ici, vous essayez de définir l'index 1 en utilisant l'index 0, les pandas affecteront des nans car il n'y a pas d'index 0 sur le côté droit de cette affectation. Souvenez-vous également que les en-têtes de colonne sont également un index. Ainsi, les pandas aligneront l'en-tête de colonne sur l'en-tête de colonne.
Scott Boston
3
Deuxièmement, l'utilisation de .iloc [i] [:] est appelée chaînage d'index et c'est généralement un assez gros "non-non" chez les pandas. Il y a quelques utilisations avec des pandas créant des vues d'un objet ou créant un nouvel objet en mémoire qui peuvent donner des résultats inattendus.
Scott Boston
N'oubliez pas de voter pour toutes les réponses utiles et d'accepter celle que vous aimez le plus. Vous le savez probablement, mais c'est pour faire savoir à la communauté quelles réponses ont été utiles et pour récompenser les gens pour leur temps et leurs efforts;) Voir ceci meta.stackexchange.com/questions/5234/ et meta.stackexchange.com/ questions / 173399 /
alan.elkin

Réponses:

3

Il y a une très, très grande différence entre series.iloc[:]et series[:], lors de l'attribution en retour. (i)locvérifie toujours pour s'assurer que tout ce que vous attribuez correspond à l'index du cessionnaire. Pendant ce temps, la [:]syntaxe est affectée au tableau NumPy sous-jacent, contournant l'alignement d'index.

s = pd.Series(index=[0, 1, 2, 3], dtype='float')  
s                                                                          

0   NaN
1   NaN
2   NaN
3   NaN
dtype: float64

# Let's get a reference to the underlying array with `copy=False`
arr = s.to_numpy(copy=False) 
arr 
# array([nan, nan, nan, nan])

# Reassign using slicing syntax
s[:] = pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])                 
s                                                                          

0    1
1    2
2    3
3    4
dtype: int64

arr 
# array([1., 2., 3., 4.]) # underlying array has changed

# Now, reassign again with `iloc`
s.iloc[:] = pd.Series([5, 6, 7, 8], index=[3, 4, 5, 6]) 
s                                                                          

0    NaN
1    NaN
2    NaN
3    5.0
dtype: float64

arr 
# array([1., 2., 3., 4.])  # `iloc` created a new array for the series
                           # during reassignment leaving this unchanged

s.to_numpy(copy=False)     # the new underlying array, for reference                                                   
# array([nan, nan, nan,  5.]) 

Maintenant que vous comprenez la différence, regardons ce qui se passe dans votre code. Imprimez simplement le RHS de vos boucles pour voir ce que vous attribuez:

for i in range(2): 
    print(a_df.iloc[0, i*4:(i+1)*4]) 

# output - first row                                                                   
0    1
1    2
2    3
3    4
Name: 0, dtype: int64
# second row. Notice the index is different
4    5
5    6
6    7
7    8
Name: 0, dtype: int64   

Lors de l'affectation à b_df.iloc[i, :]la deuxième itération, les index sont différents, donc rien n'est attribué et vous ne voyez que les NaN. Cependant, la modification b_df.iloc[i, :]de b_df.iloc[i][:]signifie que vous affectez au tableau NumPy sous-jacent, donc l'alignement d'indexation est ignoré. Cette opération est mieux exprimée comme

for i in range(2):
    b_df.iloc[i, :] = a_df.iloc[0, i*4:(i+1)*4].to_numpy()

b_df                                                                       

   0  1  2  3
0  1  2  3  4
1  5  6  7  8

Il convient également de mentionner qu'il s'agit d'une forme d'affectation chaînée, ce qui n'est pas une bonne chose , et rend également votre code plus difficile à lire et à comprendre.

cs95
la source
1
Maintenant je le comprends, merci. Avant d'attribuer la prime, pourriez-vous ajouter une référence pour cela: "la [:]syntaxe assigne au tableau NumPy sous-jacent"?
Seb
@Seb Vous ne trouverez pas vraiment de références à cela dans la documentation car il s'agit en quelque sorte d'un détail d'implémentation. Il peut être plus facile de trouver le code sur GitHub qui en est responsable, mais je pense que le moyen le plus simple est de simplement montrer ce qui se passe. J'ai édité le petit exemple en haut de ma réponse pour montrer comment le tableau sous-jacent est manipulé pendant les différents types de réaffectation. J'espère que cela rend les choses plus claires!
cs95
Merci beaucoup! C'est tellement plus clair maintenant.
Tommy Yip
0

La différence est que dans le premier cas, l'interpréteur Python a exécuté le code comme:

b_df.iloc[i,:] = a_df.iloc[0,i*4:(i+1)*4]
#as
b_df.iloc.__setitem__((i, slice(None)), value)

où la valeur serait le côté droit de l'équation. Alors que dans le second cas, l'interpréteur Python a exécuté le code comme:

b_df.iloc[i][:] = a_df.iloc[0,i*4:(i+1)*4]
#as
b_df.iloc.__getitem__(i).__setitem__(slice(None), value)

où encore la valeur serait le côté droit de l'équation.

Dans chacun de ces deux cas, une méthode différente serait appelée à l'intérieur de setitem en raison de la différence entre les clés (i, tranche (Aucun)) et tranche (Aucun) Par conséquent, nous avons un comportement différent.

MaPy
la source
b_df.iloc[i]et b_df.iloc[i][:]ont cependant les mêmes indices. Pourquoi pouvez-vous attribuer une série avec un index non correspondant à l'une mais pas à l'autre?
Seb
dans le premier cas, le _set_item serait appelé dans le second one_setitem_slice serait appelé. Donc, suspect en raison de la différence de ces méthodes, nous avons le comportement ci-dessus
MaPy
0

Quelqu'un pourrait -il me expliquer ce que la différence entre .iloc[i,:]et .iloc[i][:]est

La différence entre .iloc[i,:]et.iloc[i][:]

Dans le cas où .iloc[i,:]vous accédez directement à une possession spécifique de la DataFrame, en sélectionnant toutes les ( :) colonnes de la ie ligne. Pour autant que je sache, il est équivalent de laisser la 2ème dimension non spécifiée ( .iloc[i]).

Dans le cas où .iloc[i][:]vous effectuez 2 opérations en chaîne. Ainsi, le résultat de .iloc[i]sera alors affecté par [:]. L'utilisation de ceci pour définir des valeurs est déconseillée par Pandas lui-même ici avec un avertissement, vous ne devez donc pas l'utiliser:

Le renvoi d'une copie ou d'une référence pour une opération de paramétrage peut dépendre du contexte. Ceci est parfois appelé affectation en chaîne et doit être évité


... et pourquoi .iloc[i][:]travaillé dans mon exemple ci-dessus mais pas.iloc[i,:]

Comme @Scott l'a mentionné dans les commentaires OP, l'alignement des données est intrinsèque , donc les index dans le côté droit du =ne seront pas inclus s'ils ne sont pas présents dans le côté gauche. C'est pourquoi il y a des NaNvaleurs sur la 2ème ligne.

Donc, pour que les choses soient claires, vous pouvez procéder comme suit:

for i in range(2):
    # Get the slice
    a_slice = a_df.iloc[0, i*4:(i+1)*4]
    # Reset the indices
    a_slice.reset_index(drop=True, inplace=True)
    # Set the slice into b_df
    b_df.iloc[i,:] = a_slice

Ou vous pouvez convertir en au listlieu d'utiliser reset_index:

for i in range(2):
    # Get the slice
    a_slice = a_df.iloc[0, i*4:(i+1)*4]
    # Convert the slice into a list and set it into b_df
    b_df.iloc[i,:] = list(a_slice)
alan.elkin
la source