web-dev-qa-db-ja.com

pandas関数を列に適用して、複数の新しい列を作成しますか?

パンダでこれを行う方法:

単一のテキスト列に関数extract_text_featuresがあり、複数の出力列を返します。具体的には、関数は6つの値を返します。

関数は動作しますが、適切な戻り値の型(pandas DataFrame/numpy array/Python list)が存在しないようで、出力にdf.ix[: ,10:16] = df.textcol.map(extract_text_features)が正しく割り当てられます。

this ?のように、df.iterrows()での反復処理に戻る必要があると思います。

更新:df.iterrows()を使用した反復は少なくとも20倍遅くなります。そのため、関数を放棄し、6つの異なる.map(lambda ...)呼び出しに分割しました。

更新2:この質問は、 v0.11. の前後に再度質問されました。したがって、質問と回答の多くはあまり関連性がありません。

156
smci

User1827356の答えを基に、df.mergeを使用して1つのパスで割り当てを行うことができます。

df.merge(df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1})), 
    left_index=True, right_index=True)

    textcol  feature1  feature2
0  0.772692  1.772692 -0.227308
1  0.857210  1.857210 -0.142790
2  0.065639  1.065639 -0.934361
3  0.819160  1.819160 -0.180840
4  0.088212  1.088212 -0.911788
84
Zelazny7

私は通常Zipを使用してこれを行います:

>>> df = pd.DataFrame([[i] for i in range(10)], columns=['num'])
>>> df
    num
0    0
1    1
2    2
3    3
4    4
5    5
6    6
7    7
8    8
9    9

>>> def powers(x):
>>>     return x, x**2, x**3, x**4, x**5, x**6

>>> df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
>>>     Zip(*df['num'].map(powers))

>>> df
        num     p1      p2      p3      p4      p5      p6
0       0       0       0       0       0       0       0
1       1       1       1       1       1       1       1
2       2       2       4       8       16      32      64
3       3       3       9       27      81      243     729
4       4       4       16      64      256     1024    4096
5       5       5       25      125     625     3125    15625
6       6       6       36      216     1296    7776    46656
7       7       7       49      343     2401    16807   117649
8       8       8       64      512     4096    32768   262144
9       9       9       81      729     6561    59049   531441
139
ostrokach

これは私が過去にやったことです

df = pd.DataFrame({'textcol' : np.random.Rand(5)})

df
    textcol
0  0.626524
1  0.119967
2  0.803650
3  0.100880
4  0.017859

df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))
   feature1  feature2
0  1.626524 -0.373476
1  1.119967 -0.880033
2  1.803650 -0.196350
3  1.100880 -0.899120
4  1.017859 -0.982141

完全性のための編集

pd.concat([df, df.textcol.apply(lambda s: pd.Series({'feature1':s+1, 'feature2':s-1}))], axis=1)
    textcol feature1  feature2
0  0.626524 1.626524 -0.373476
1  0.119967 1.119967 -0.880033
2  0.803650 1.803650 -0.196350
3  0.100880 1.100880 -0.899120
4  0.017859 1.017859 -0.982141
69
user1827356

これは、95%のユースケースでこれを達成するための正しい最も簡単な方法です。

>>> df = pd.DataFrame(Zip(*[range(10)]), columns=['num'])
>>> df
    num
0    0
1    1
2    2
3    3
4    4
5    5

>>> def example(x):
...     x['p1'] = x['num']**2
...     x['p2'] = x['num']**3
...     x['p3'] = x['num']**4
...     return x

>>> df = df.apply(example, axis=1)
>>> df
    num  p1  p2  p3
0    0   0   0    0
1    1   1   1    1
2    2   4   8   16
3    3   9  27   81
4    4  16  64  256
50

概要:いくつかの列のみを作成する場合は、df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)を使用します

このソリューションでは、作成する新しい列の数は、.apply()関数への入力として使用する列の数と等しくなければなりません。他の何かをしたい場合は、他の答えを見てください。

詳細 2列のデータフレームがあるとします。最初の列は、10歳のときの人の身長です。 2番目は20歳のときの人の身長です。

各人の身長の平均と各人の身長の合計の両方を計算する必要があるとします。それは各行ごとに2つの値です。

これは、すぐに適用される次の関数を使用して実行できます。

def mean_and_sum(x):
    """
    Calculates the mean and sum of two heights.
    Parameters:
    :x -- the values in the row this function is applied to. Could also work on a list or a Tuple.
    """

    sum=x[0]+x[1]
    mean=sum/2
    return [mean,sum]

この関数は次のように使用できます。

 df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)

(明確にするために:この適用関数は、サブセット化されたデータフレームの各行から値を取得し、リストを返します。)

ただし、これを行う場合:

df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)

[mean、sum]リストを含む1つの新しい列を作成します。これはおそらく別のLambda/Applyが必要になるため、避けたいと思うでしょう。

代わりに、各値を独自の列に分割します。これを行うには、2つの列を一度に作成できます。

df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']]
.apply(mean_and_sum(x),axis=1)
14
Evan W.

これを行ういくつかの方法を検討しましたが、ここに示す方法(pandasシリーズを返す)は、最も効率的ではないようです。

ランダムデータの大規模なデータフレームで開始する場合:

# Setup a dataframe of random numbers and create a 
df = pd.DataFrame(np.random.randn(10000,3),columns=list('ABC'))
df['D'] = df.apply(lambda r: ':'.join(map(str, (r.A, r.B, r.C))), axis=1)
columns = 'new_a', 'new_b', 'new_c'

以下に例を示します。

# Create the dataframe by returning a series
def method_b(v):
    return pd.Series({k: v for k, v in Zip(columns, v.split(':'))})
%timeit -n10 -r3 df.D.apply(method_b)

10ループ、最高3:ループあたり2.77秒

別の方法:

# Create a dataframe from a series of tuples
def method_a(v):
    return v.split(':')
%timeit -n10 -r3 pd.DataFrame(df.D.apply(method_a).tolist(), columns=columns)

10ループ、ベスト3:ループあたり8.85ミリ秒

私の推測では、一連のタプルを取得して、それをDataFrameに変換する方がはるかに効率的です。私の仕事に誤りがあったとしても、人々の考えを聞いてみたいと思います。

10
RFox

2018年、引数result_type='expand'apply()を使用します

>>> appiled_df = df.apply(lambda row: fn(row.text), axis='columns', result_type='expand')
>>> df = pd.concat([df, appiled_df], axis='columns')
9
Ben

私にとってこれはうまくいきました:

入力df

df = pd.DataFrame({'col x': [1,2,3]})
   col x
0      1
1      2
2      3

関数

def f(x):
    return pd.Series([x*x, x*x*x])

2つの新しい列を作成します。

df[['square x', 'cube x']] = df['col x'].apply(f)

出力:

   col x  square x  cube x
0      1         1       1
1      2         4       8
2      3         9      27
9
Joe

受け入れられたソリューションは、大量のデータに対して非常に遅くなります。投票数が最大のソリューションは、読み取りが少し難しく、数値データの場合も遅くなります。各新しい列を他の列とは独立して計算できる場合は、applyを使用せずに各列を直接割り当てます。

偽の文字データの例

DataFrameに100,000文字列を作成します

df = pd.DataFrame(np.random.choice(['he jumped', 'she ran', 'they hiked'],
                                   size=100000, replace=True),
                  columns=['words'])
df.head()
        words
0     she ran
1     she ran
2  they hiked
3  they hiked
4  they hiked

元の質問で行ったように、いくつかのテキスト機能を抽出したいとしましょう。たとえば、最初の文字を抽出し、文字 'e'の出現回数をカウントして、フレーズを大文字にしましょう。

df['first'] = df['words'].str[0]
df['count_e'] = df['words'].str.count('e')
df['cap'] = df['words'].str.capitalize()
df.head()
        words first  count_e         cap
0     she ran     s        1     She ran
1     she ran     s        1     She ran
2  they hiked     t        2  They hiked
3  they hiked     t        2  They hiked
4  they hiked     t        2  They hiked

タイミング

%%timeit
df['first'] = df['words'].str[0]
df['count_e'] = df['words'].str.count('e')
df['cap'] = df['words'].str.capitalize()
127 ms ± 585 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

def extract_text_features(x):
    return x[0], x.count('e'), x.capitalize()

%timeit df['first'], df['count_e'], df['cap'] = Zip(*df['words'].apply(extract_text_features))
101 ms ± 2.96 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

驚いたことに、各値をループすることでパフォーマンスを向上させることができます

%%timeit
a,b,c = [], [], []
for s in df['words']:
    a.append(s[0]), b.append(s.count('e')), c.append(s.capitalize())

df['first'] = a
df['count_e'] = b
df['cap'] = c
79.1 ms ± 294 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

偽の数値データを使用した別の例

100万個の乱数を作成し、上記のpowers関数をテストします。

df = pd.DataFrame(np.random.Rand(1000000), columns=['num'])


def powers(x):
    return x, x**2, x**3, x**4, x**5, x**6

%%timeit
df['p1'], df['p2'], df['p3'], df['p4'], df['p5'], df['p6'] = \
       Zip(*df['num'].map(powers))
1.35 s ± 83.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

各列の割り当ては25倍高速で非常に読みやすくなっています。

%%timeit 
df['p1'] = df['num'] ** 1
df['p2'] = df['num'] ** 2
df['p3'] = df['num'] ** 3
df['p4'] = df['num'] ** 4
df['p5'] = df['num'] ** 5
df['p6'] = df['num'] ** 6
51.6 ms ± 1.9 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

applyが一般的に進むべき道ではない理由について、 詳細はこちら で同様の応答をしました。

8
Ted Petrou

他の2つの同様の質問に同じ回答を投稿しました。私がこれを行うことを好む方法は、関数の戻り値を一連にまとめることです:

def f(x):
    return pd.Series([x**2, x**3])

次に、次のようにapplyを使用して、個別の列を作成します。

df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)
6
Dmytro Bugayev

result_type="expand"を使用するだけです

df = pd.DataFrame(np.random.randint(0,10,(10,2)), columns=["random", "a"])
df[["sq_a","cube_a"]] = df.apply(lambda x: [x.a**2, x.a**3], axis=1, result_type="expand")
1
Abhishek

値の代わりに行全体を返すことができます:

df = df.apply(extract_text_features,axis = 1)

関数は行を返します

def extract_text_features(row):
      row['new_col1'] = value1
      row['new_col2'] = value2
      return row
1
Saket Bajaj