Применить функцию панды к столбцу, чтобы создать несколько новых столбцов?
как это сделать в панд:
у меня есть функция extract_text_features в одном текстовом столбце, возвращая несколько выходных столбцов. В частности, функция возвращает 6 значений.
функция работает, однако, похоже, что нет никакого правильного типа возврата (pandas DataFrame / numpy array / Python list), так что выход может быть правильно назначен df.ix[: ,10:16] = df.textcol.map(extract_text_features)
поэтому я думаю, что мне нужно вернуться к итерации с df.iterrows(), согласно этой?
обновление:
Итерация с df.iterrows() по крайней мере в 20 раз медленнее, поэтому я сдался и разделил функцию на шесть различных .map(lambda ...) звонки.
10 ответов:
построение ответа user1827356, вы можете выполнить задание за один проход, используя
df.merge: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
Я обычно делаю это с помощью
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
это то, что я сделал в прошлом
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
Это правильный и самый простой способ сделать это для 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
резюме: если вы хотите создать несколько столбцов, используйте
df[['new_col1','new_col2']] = df[['data1','data2']].apply( function_of_your_choosing(x), axis=1)для этого решения количество новых столбцов, которые вы создаете, должно быть равно количеству столбцов, которые вы используете в качестве входных данных .применить функцию (). Если вы хотите сделать что-то еще, посмотрите на другие ответы.
подробности Допустим, у вас есть двухколоночный фрейм данных. Первый столбец-это высота человека, когда им 10 лет; второй-это высота человека, когда они на 20.
Предположим, вам нужно рассчитать как среднее значение высоты каждого человека, так и сумму высот каждого человека. Это два значения в каждой строке.
вы можете сделать это с помощью следующей функции, которая скоро будет применена:
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)(чтобы быть ясным: эта функция apply принимает значения из каждой строки в подмножестве dataframe и возвращает список.)
однако, если вы делаете это:
df['Mean_&_Sum'] = df[['height_at_age_10','height_at_age_20']].apply(mean_and_sum(x),axis=1)вы создадите 1 новый столбец, содержащий списки [mean,sum], которые вы предположительно хотите избежать, потому что для этого потребуется еще одна лямбда/применить.
вместо этого вы хотите разбить каждое значение на свой собственный столбец. Для этого можно создать сразу два столбца:
df[['Mean','Sum']] = df[['height_at_age_10','height_at_age_20']] .apply(mean_and_sum(x),axis=1)
Я посмотрел несколько способов сделать это, и метод, показанный здесь (возвращение серии панд), не кажется наиболее эффективным.
если мы начнем с большого кадра случайных данных:
# 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 s на петлю
альтернативный метод:
# 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 МС на петлю
мой считая, что гораздо эффективнее взять серию кортежей, а затем преобразовать ее в фрейм данных. Мне было бы интересно услышать мнение людей, хотя, если есть ошибка в моей работе.
принятое решение будет очень медленным для большого количества данных. Решение с наибольшим количеством upvotes немного трудно читать, а также медленно с числовыми данными. Если каждый новый столбец может быть вычислен независимо от других, я бы просто назначил каждый из них напрямую без использования
apply.пример с поддельными данными символов
создать 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)еще один пример с поддельными числовыми данными
создать 1 миллион случайных чисел и тестирования
В 2018 году, я использую
apply()С аргументомresult_type='expand'>>> appiled_df = df.apply(lambda row: fn(row.text), axis='columns', result_type='expand') >>> df = pd.concat([df, appiled_df], axis='columns')
вы можете вернуть всю строку вместо значений:
df = df.apply(extract_text_features,axis = 1)где функция возвращает строку
def extract_text_features(row): row['new_col1'] = value1 row['new_col2'] = value2 return row
опубликовали тот же ответ в двух других подобных вопросах. Я предпочитаю сделать это, чтобы обернуть возвращаемые значения функции в ряд:
def f(x): return pd.Series([x**2, x**3])а затем используйте применить следующим образом для создания отдельных столбцов:
df[['x**2','x**3']] = df.apply(lambda row: f(row['x']), axis=1)
Comments