python pandasデータフレーム、値渡しか参照渡しか


88

データフレームを関数に渡し、関数内で変更した場合、値渡しですか、それとも参照渡しですか?

次のコードを実行します

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
def letgo(df):
    df = df.drop('b',axis=1)
letgo(a)

の値はa、関数呼び出し後も変更されません。それは値渡しであることを意味しますか?

私も以下を試しました

xx = np.array([[1,2], [3,4]])
def letgo2(x):
    x[1,1] = 100
def letgo3(x):
    x = np.array([[3,3],[3,3]])

それは判明letgo2()変更を行いxxそしてletgo3()ません。なんでこんな感じ?


回答:


95

簡単に言うと、Pythonは常に値渡しを行いますが、すべてのPython変数は実際にはオブジェクトへのポインターであるため、参照渡しのように見える場合があります。

Pythonでは、すべてのオブジェクトは可変または非可変のいずれかです。たとえば、リスト、辞書、モジュール、パンダのデータフレームは変更可能であり、int、文字列、タプルは変更できません。可変オブジェクトは内部で変更できますが(たとえば、リストに要素を追加する)、非可変オブジェクトは変更できません。

冒頭で述べたように、すべてのPython変数はオブジェクトへのポインターと考えることができます。変数を関数に渡す場合、関数内の変数(ポインター)は常に渡された変数(ポインター)のコピーです。したがって、内部変数に新しいものを割り当てる場合は、変更するだけです。別のオブジェクトを指すローカル変数。これは、変数が指している元のオブジェクトを変更(変更)したり、外部変数が新しいオブジェクトを指したりすることはありません。この時点で、外部変数は元のオブジェクトを指していますが、内部変数は新しいオブジェクトを指しています。

元のオブジェクトを変更する場合(可変データ型でのみ可能)、ローカル変数に完全に新しい値を割り当てずに、オブジェクトを変更する何かを行う必要があります。これが理由letgo()letgo3()、外部アイテムは変更されないままですが、letgo2()変更されます。

@ursanが指摘したletgo()ように、代わりにこのようなものを使用dfすると、それが指す元のオブジェクトが変更(変更)され、グローバルa変数を介して表示される値が変更されます。

def letgo(df):
    df.drop('b', axis=1, inplace=True)

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
letgo(a)  # will alter a

場合によっては、実際に直接割り当てを行わなくても、元の変数を完全にくり抜いて新しいデータを再入力できます。たとえばv、これにより、ポイントする元のオブジェクトが変更され、v後で使用するときに表示されるデータが変更されます。

def letgo3(x):
    x[:] = np.array([[3,3],[3,3]])

v = np.empty((2, 2))
letgo3(v)   # will alter v

x;に直接何かを割り当てていないことに注意してください。の内部範囲全体に何かを割り当てていxます。

どうしても完全に新しいオブジェクトを作成して外部に表示する必要がある場合(パンダの場合もあります)、2つのオプションがあります。'clean'オプションは、新しいオブジェクトを返すだけです。

def letgo(df):
    df = df.drop('b',axis=1)
    return df

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
a = letgo(a)

もう1つのオプションは、関数の外部に到達して、グローバル変数を直接変更することです。これaは新しいオブジェクトを指すように変更され、a後で参照する関数はその新しいオブジェクトを参照します。

def letgo():
    global a
    a = a.drop('b',axis=1)

a = pd.DataFrame({'a':[1,2], 'b':[3,4]})
letgo()   # will alter a!

コードを読む人は誰でもどのようaに変更されたかを理解するのに苦労するので、グローバル変数を直接変更することは通常悪い考えです。(私は通常、スクリプト内の多くの関数で使用される共有パラメーターにグローバル変数を使用しますが、それらにそれらのグローバル変数を変更させません。)


8

非常に良い読み物を指摘した@MikeGrahamの答えに追加するには:

あなたの場合、覚えておくべき重要なことは、名前値の違いですadfxxx、全てある名前が、それらは同一でも異なっを参照するあなたの例の異なる点で:

  • 最初の例では、引数を設定しない限りnewを返すため、別の値にletgo 再バインド dfします(docを参照)。これは、の値を参照していた名前(関数のローカル)が新しい値(ここでは戻り値)を参照していることを意味します。参照している値はまだ存在し、変更されていません。df.dropDataFrameinplace = Truedfletgoadf.dropa

  • 第2の例では、letgo2 変異し xた理由であり、それを再結合することなく、xxによって変更されますletgo2。前の例とは異なり、ここではローカル名はx常に名前xxが参照している値を参照し、その値をその場xx変更します。そのため、参照している値が変更されています。

  • 3番目の例では、新しいにletgo3 再バインド xnp.arrayます。これによりx、ローカルletgo3で以前はの値を参照していた名前が、xx別の値であるnewを参照するようになりますnp.arrayxx参照している値は変更されていません。


7

問題はPBV対PBRではありません。これらの名前は、Pythonのような言語でのみ混乱を引き起こします。それらは、CまたはFortranのように機能する言語(典型的なPBVおよびPBR言語として)のために発明されました。Pythonが常に値を渡すことは事実ですが、啓蒙的ではありません。ここでの問題は、値自体が変更されているかどうか、または新しい値を取得するかどうかです。パンダは通常、後者の側で誤りを犯します。

http://nedbatchelder.com/text/names.htmlは、Pythonの名前のシステムが何であるかを非常によく説明しています。


1
Pythonでの受け渡しと割り当てのセマンティクスは、Javaの場合とまったく同じであり、あなたが言うのと同じことをJavaにも同様に適用できます。しかし、StackOverflowやインターネット上の他の場所では、この問題が発生するたびにJavaが常に価値を渡すことを印象付けることが「啓発的」であると人々は明らかに感じています。
newacct 2016

3

Pythonは、値渡しでも参照渡しでもありません。割り当て渡しです。

サポートリファレンス、Python FAQ:https//docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

IOW:

  1. 不変の値を渡した場合、名前を新しいオブジェクトに再バインドしているため、値を変更しても呼び出し元の値は変更されません。
  2. 可変値を渡すと、呼び出された関数で行われた変更は、その名前を新しいオブジェクトに再バインドしない限り、呼び出し元の値も変更します。変数を再割り当てして新しいオブジェクトを作成すると、その変更とその後の名前の変更は呼び出し元に表示されません。

したがって、リストを渡し、その0番目の値を変更すると、その変更は呼び出し元と呼び出し元の両方に見られます。ただし、リストを新しいリストに再割り当てすると、この変更は失われます。ただし、リストをスライスして新しいリストに置き換える、その変更は呼び出し元と呼び出し元の両方に表示されます。

例えば:

def change_it(list_):
    # This change would be seen in the caller if we left it alone
    list_[0] = 28

    # This change is also seen in the caller, and replaces the above
    # change
    list_[:] = [1, 2]

    # This change is not seen in the caller.
    # If this were pass by reference, this change too would be seen in
    # caller.
    list_ = [3, 4]

thing = [10, 20]
change_it(thing)
# here, thing is [1, 2]

あなたがCファンなら、これは値によるポインターの受け渡しと考えることができます-値へのポインターへのポインターではなく、値へのポインターだけです。

HTH。


0

ドロップのドキュメントは次のとおりです。

要求された軸のラベルが削除された新しいオブジェクトを返します。

そのため、新しいデータフレームが作成されます。オリジナルは変更されていません。

ただし、Pythonのすべてのオブジェクトについては、データフレームは参照によって関数に渡されます。


しかしdf、関数内に割り当てました。参照値が新しいオブジェクトに変更されたという意味ではありませんか?
nos 2016

ローカル名に割り当てても、名前が別のスコープでバインドされているオブジェクトが変更されることはありません。
マイクグラハム

0

関数の開始時に「a」をグローバルにする必要があります。そうしないと、ローカル変数であり、メインコードの「a」は変更されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.