Объедините два столбца текста в кадре данных pandas

684

У меня есть фрейм данных 20 x 4000 в Python с использованием pandas. Два из этих столбцов названы Yearи quarter. Я хотел бы создать переменную, periodкоторая делает Year = 2000и quarter= q2в 2000q2.

Кто-нибудь может с этим помочь?

0
810

если оба столбца являются строками, их можно напрямую объединить:

df["period"] = df["Year"] + df["quarter"]

Если один (или оба) столбца не являются строковыми, вы должны сначала преобразовать его (их),

df["period"] = df["Year"].astype(str) + df["quarter"]

При этом остерегайтесь NaN!


Если вам нужно объединить несколько строковых столбцов, вы можете использовать agg:

df['period'] = df[['Year', 'quarter', ...]].agg('-'.join, axis=1)

Где «-» - разделитель.

16
  • 17
    Можно ли сложить несколько столбцов вместе, не набирая все столбцы? Скажем add(dataframe.iloc[:, 0:10])например? Heisenberg 9 мая '15 в 19:15
  • 5
    @Heisenberg Это должно быть возможно с помощью встроенного Python sum. silvado 11 мая '15 в 11:06
  • 6
    @silvado не могли бы вы сделать пример для добавления нескольких столбцов? Спасибоc1c1c1 25 окт.
  • 7
    Будьте осторожны, вам нужно применить map (str) ко всем столбцам, которые изначально не являются строковыми. если бы квартал был числом, вы бы сделали dataframe["period"] = dataframe["Year"].map(str) + dataframe["quarter"].map(str)карту, просто применяя преобразование строки ко всем записям. Ozgur Ozturk 1 фев '17 в 21:17
  • 15
    Это решение может создать проблемы, если у вас есть значения nan, осторожноuser2270655 27 дек.
335

Небольшие наборы данных (<150 строк)

[''.join(i) for i in zip(df["Year"].map(str),df["quarter"])]

или немного медленнее, но компактнее:

df.Year.str.cat(df.quarter)

Большие наборы данных (> 150 строк)

df['Year'].astype(str) + df['quarter']

ОБНОВЛЕНИЕ: График времени Pandas 0.23.4

введите описание изображения здесь

Давайте протестируем это на 200K строках DF:

In [250]: df
Out[250]:
   Year quarter
0  2014      q1
1  2015      q2

In [251]: df = pd.concat([df] * 10**5)

In [252]: df.shape
Out[252]: (200000, 2)

ОБНОВЛЕНИЕ: новые тайминги с использованием Pandas 0.19.0

Время без оптимизации CPU / GPU (отсортировано от самого быстрого к самому медленному):

In [107]: %timeit df['Year'].astype(str) + df['quarter']
10 loops, best of 3: 131 ms per loop

In [106]: %timeit df['Year'].map(str) + df['quarter']
10 loops, best of 3: 161 ms per loop

In [108]: %timeit df.Year.str.cat(df.quarter)
10 loops, best of 3: 189 ms per loop

In [109]: %timeit df.loc[:, ['Year','quarter']].astype(str).sum(axis=1)
1 loop, best of 3: 567 ms per loop

In [110]: %timeit df[['Year','quarter']].astype(str).sum(axis=1)
1 loop, best of 3: 584 ms per loop

In [111]: %timeit df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)
1 loop, best of 3: 24.7 s per loop

Время с использованием оптимизации CPU / GPU:

In [113]: %timeit df['Year'].astype(str) + df['quarter']
10 loops, best of 3: 53.3 ms per loop

In [114]: %timeit df['Year'].map(str) + df['quarter']
10 loops, best of 3: 65.5 ms per loop

In [115]: %timeit df.Year.str.cat(df.quarter)
10 loops, best of 3: 79.9 ms per loop

In [116]: %timeit df.loc[:, ['Year','quarter']].astype(str).sum(axis=1)
1 loop, best of 3: 230 ms per loop

In [117]: %timeit df[['Year','quarter']].astype(str).sum(axis=1)
1 loop, best of 3: 230 ms per loop

In [118]: %timeit df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)
1 loop, best of 3: 9.38 s per loop

Ответить на вклад @ anton-vbr

16
  • Какая разница между 261 и 264 по вашему времени? Anton Protopopov 21 мая '16 в 19:57
  • @AntonProtopopov, видимо, 100 мс из ниоткуда :)Dennis Golomazov 10 окт.
  • @AntonProtopopov, я думаю, это смесь двух таймингов: один использовал оптимизацию CPU / GPU, а другой - нет. Я обновил свой ответ и поместил туда оба набора времени ...MaxU 10 окт.
  • Такое использование .sum () не работает, если все столбцы выглядят так, как будто они могут быть целыми числами (т.е. являются строковыми формами целых чисел). Вместо этого кажется, что панды конвертируют их обратно в числа перед суммированием! CPBL 25 мая '17 в 13:06
  • 1
    @MaxU Как вы подошли к оптимизации CPU / GPU? Это просто более мощный компьютер или это то, что вы сделали с кодом? user3374113 07 июл.
304
df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
df['period'] = df[['Year', 'quarter']].apply(lambda x: ''.join(x), axis=1)

Дает этот фрейм данных

   Year quarter  period
0  2014      q1  2014q1
1  2015      q2  2015q2

Этот метод обобщается на произвольное количество строковых столбцов путем замены df[['Year', 'quarter']]на любой фрагмент столбца вашего фрейма данных, например df.iloc[:,0:2].apply(lambda x: ''.join(x), axis=1).

Вы можете узнать больше о методе apply () здесь.

11
  • 25
    lambda x: ''.join(x)просто ''.join, нет? DSM 19 сен '16 в 11:54
  • 6
    @OzgurOzturk: ​​дело в том, что лямбда-часть lambda x: ''.join(x)конструкции ничего не делает; это похоже на использование lambda x: sum(x)вместо просто sum. DSM 01 фев '17 в 21:07
  • 4
    Подтверждено же результат при использовании ''.join, а именно: df['period'] = df[['Year', 'quarter']].apply(''.join, axis=1). Max Ghenis 10 окт.
  • 1
    @Archie joinпринимает только strэкземпляры в итерации. Используйте a, mapчтобы преобразовать их все в, strа затем использовать join. John Strood 27 марта '18 в 12:51
  • 19
    '-'. join (x.map (str))Manjul 3 сен '18 в 8:23
178

Для cat()этого.str очень хорошо работает метод аксессуара :

>>> import pandas as pd
>>> df = pd.DataFrame([["2014", "q1"], 
...                    ["2015", "q3"]],
...                   columns=('Year', 'Quarter'))
>>> print(df)
   Year Quarter
0  2014      q1
1  2015      q3
>>> df['Period'] = df.Year.str.cat(df.Quarter)
>>> print(df)
   Year Quarter  Period
0  2014      q1  2014q1
1  2015      q3  2015q3

cat() даже позволяет вам добавить разделитель, поэтому, например, предположим, что у вас есть только целые числа для года и периода, вы можете сделать это:

>>> import pandas as pd
>>> df = pd.DataFrame([[2014, 1],
...                    [2015, 3]],
...                   columns=('Year', 'Quarter'))
>>> print(df)
   Year Quarter
0  2014       1
1  2015       3
>>> df['Period'] = df.Year.astype(str).str.cat(df.Quarter.astype(str), sep='q')
>>> print(df)
   Year Quarter  Period
0  2014       1  2014q1
1  2015       3  2015q3

Для объединения нескольких столбцов достаточно передать либо список серий, либо фрейм данных, содержащий все столбцы, кроме первого, в качестве параметра, str.cat()вызываемого в первом столбце (серии):

>>> df = pd.DataFrame(
...     [['USA', 'Nevada', 'Las Vegas'],
...      ['Brazil', 'Pernambuco', 'Recife']],
...     columns=['Country', 'State', 'City'],
... )
>>> df['AllTogether'] = df['Country'].str.cat(df[['State', 'City']], sep=' - ')
>>> print(df)
  Country       State       City                   AllTogether
0     USA      Nevada  Las Vegas      USA - Nevada - Las Vegas
1  Brazil  Pernambuco     Recife  Brazil - Pernambuco - Recife

Обратите внимание, что если ваш фрейм данных / серия pandas имеет нулевые значения, вам необходимо включить параметр na_rep, чтобы заменить значения NaN строкой, в противном случае объединенный столбец будет по умолчанию на NaN.

7
  • 16
    Это кажется намного лучше (может быть, более эффективным), чем lambdaили map; также он просто читается наиболее чисто. dwanderson 22 мая '16 в 20:31
  • 1
    @ZakS, передав оставшиеся столбцы в виде фрейма данных вместо ряда в качестве первого параметра в str.cat(). Исправлю ответLeoRochael 23 июля '18 в 21: 422018-07-23 21:42
  • Какую версию панд вы используете? Я получаю ValueError: Вы хотели указать sepключевое слово? в пандах-0.23.4. Спасибо! Qinqing Liu 5 дек '18 в 20:56
  • @QinqingLiu, я повторно протестировал их с помощью pandas-0.23.4, и они, похоже, работают. sepПараметр необходим только если вы намерены отделить части сцепленной строки. Если вы получили сообщение об ошибке, покажите нам пример ошибки. LeoRochael 10 дек '18 в 19:34
  • 1
    @ arun-menon: Я не понимаю, почему бы и нет. В последнем примере выше вы могли бы сделать .str.cat(df[['State', 'City']], sep ='\n'), например. Однако я его еще не тестировал. LeoRochael 21 июня в 12:08
36

На этот раз использование функции lamba с string.format ().

import pandas as pd
df = pd.DataFrame({'Year': ['2014', '2015'], 'Quarter': ['q1', 'q2']})
print df
df['YearQuarter'] = df[['Year','Quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)
print df

  Quarter  Year
0      q1  2014
1      q2  2015
  Quarter  Year YearQuarter
0      q1  2014      2014q1
1      q2  2015      2015q2

Это позволяет вам работать с нестроковыми значениями и при необходимости переформатировать значения.

import pandas as pd
df = pd.DataFrame({'Year': ['2014', '2015'], 'Quarter': [1, 2]})
print df.dtypes
print df

df['YearQuarter'] = df[['Year','Quarter']].apply(lambda x : '{}q{}'.format(x[0],x[1]), axis=1)
print df

Quarter     int64
Year       object
dtype: object
   Quarter  Year
0        1  2014
1        2  2015
   Quarter  Year YearQuarter
0        1  2014      2014q1
1        2  2015      2015q2
1
  • 4
    Намного быстрее: .apply (''. Join (x), axis = 1)Minions 08 июл.
18

обобщая на несколько столбцов, почему бы и нет:

columns = ['whatever', 'columns', 'you', 'choose']
df['period'] = df[columns].astype(str).sum(axis=1)
1
  • 4
    Выглядит круто, но что, если я хочу добавить разделитель между строками, например '-'? Odisseo 2 окт '19 в 17:55
14

Хотя ответ @silvado хорош, если вы перейдете df.map(str)на df.astype(str)него, он будет быстрее:

import pandas as pd
df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})

In [131]: %timeit df["Year"].map(str)
10000 loops, best of 3: 132 us per loop

In [132]: %timeit df["Year"].astype(str)
10000 loops, best of 3: 82.2 us per loop
13

Предположим, у вас dataframeесть dfстолбцы Yearи Quarter.

import pandas as pd
df = pd.DataFrame({'Quarter':'q1 q2 q3 q4'.split(), 'Year':'2000'})

Предположим, мы хотим увидеть фрейм данных;

df
>>>  Quarter    Year
   0    q1      2000
   1    q2      2000
   2    q3      2000
   3    q4      2000

Наконец, соедините Yearи Quarterследующим образом.

df['Period'] = df['Year'] + ' ' + df['Quarter']

Теперь вы можете print df увидеть получившийся фрейм данных.

df
>>>  Quarter    Year    Period
    0   q1      2000    2000 q1
    1   q2      2000    2000 q2
    2   q3      2000    2000 q3
    3   q4      2000    2000 q4

Если вам не нужен промежуток между годом и кварталом, просто удалите его, выполнив;

df['Period'] = df['Year'] + df['Quarter']
5
  • 3
    Указывается как строкиdf['Period'] = df['Year'].map(str) + df['Quarter'].map(str)Stuber 07 авг.
  • Получаю TypeError: Series cannot perform the operation +когда бегу либо df2['filename'] = df2['job_number'] + '.' + df2['task_number']либо df2['filename'] = df2['job_number'].map(str) + '.' + df2['task_number'].map(str). Karl Baker 03 мар.
  • Однако df2['filename'] = df2['job_number'].astype(str) + '.' + df2['task_number'].astype(str)сработало. Karl Baker 03 мар.
  • @KarlBaker, я думаю, у вас не было строк в вашем вводе. Но я рад, что ты это понял. Если вы посмотрите на пример, dataframeкоторый я создал выше, вы увидите, что все столбцы имеют stringразмер s. Samuel Nde 03 марта '19 в 17:31
  • В чем именно смысл этого решения, если он идентичен верхнему ответу? AMC 18 мар.
11

Вот реализация, которую я считаю очень универсальной:

In [1]: import pandas as pd 

In [2]: df = pd.DataFrame([[0, 'the', 'quick', 'brown'],
   ...:                    [1, 'fox', 'jumps', 'over'], 
   ...:                    [2, 'the', 'lazy', 'dog']],
   ...:                   columns=['c0', 'c1', 'c2', 'c3'])

In [3]: def str_join(df, sep, *cols):
   ...:     from functools import reduce
   ...:     return reduce(lambda x, y: x.astype(str).str.cat(y.astype(str), sep=sep), 
   ...:                   [df[col] for col in cols])
   ...: 

In [4]: df['cat'] = str_join(df, '-', 'c0', 'c1', 'c2', 'c3')

In [5]: df
Out[5]: 
   c0   c1     c2     c3                cat
0   0  the  quick  brown  0-the-quick-brown
1   1  fox  jumps   over   1-fox-jumps-over
2   2  the   lazy    dog     2-the-lazy-dog
1
  • К вашему сведению: этот метод отлично работает с Python 3, но вызывает у меня проблемы с Python 2.Alex P. Miller 31 июл.
11

более эффективно

def concat_df_str1(df):
    """ run time: 1.3416s """
    return pd.Series([''.join(row.astype(str)) for row in df.values], index=df.index)

а вот и временная проверка:

import numpy as np
import pandas as pd

from time import time


def concat_df_str1(df):
    """ run time: 1.3416s """
    return pd.Series([''.join(row.astype(str)) for row in df.values], index=df.index)


def concat_df_str2(df):
    """ run time: 5.2758s """
    return df.astype(str).sum(axis=1)


def concat_df_str3(df):
    """ run time: 5.0076s """
    df = df.astype(str)
    return df[0] + df[1] + df[2] + df[3] + df[4] + \
           df[5] + df[6] + df[7] + df[8] + df[9]


def concat_df_str4(df):
    """ run time: 7.8624s """
    return df.astype(str).apply(lambda x: ''.join(x), axis=1)


def main():
    df = pd.DataFrame(np.zeros(1000000).reshape(100000, 10))
    df = df.astype(int)

    time1 = time()
    df_en = concat_df_str4(df)
    print('run time: %.4fs' % (time() - time1))
    print(df_en.head(10))


if __name__ == '__main__':
    main()

final, когда используется sum(concat_df_str2), результат не будет просто concat, он будет преобразован в целое число.

1
  • 1
    +1 Отличное решение, это также позволяет нам указывать столбцы: например, df.values[:, 0:3]или df.values[:, [0,2]]. Snow bunting 9 фев '18 в 9:51
11

Вы можете использовать лямбда:

combine_lambda = lambda x: '{}{}'.format(x.Year, x.quarter)

А затем используйте его для создания нового столбца:

df['period'] = df.apply(combine_lambda, axis = 1)
7

Использование zipмогло быть еще быстрее:

df["period"] = [''.join(i) for i in zip(df["Year"].map(str),df["quarter"])]

График:

введите описание изображения здесь

import pandas as pd
import numpy as np
import timeit
import matplotlib.pyplot as plt
from collections import defaultdict

df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})

myfuncs = {
"df['Year'].astype(str) + df['quarter']":
    lambda: df['Year'].astype(str) + df['quarter'],
"df['Year'].map(str) + df['quarter']":
    lambda: df['Year'].map(str) + df['quarter'],
"df.Year.str.cat(df.quarter)":
    lambda: df.Year.str.cat(df.quarter),
"df.loc[:, ['Year','quarter']].astype(str).sum(axis=1)":
    lambda: df.loc[:, ['Year','quarter']].astype(str).sum(axis=1),
"df[['Year','quarter']].astype(str).sum(axis=1)":
    lambda: df[['Year','quarter']].astype(str).sum(axis=1),
    "df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1)":
    lambda: df[['Year','quarter']].apply(lambda x : '{}{}'.format(x[0],x[1]), axis=1),
    "[''.join(i) for i in zip(dataframe['Year'].map(str),dataframe['quarter'])]":
    lambda: [''.join(i) for i in zip(df["Year"].map(str),df["quarter"])]
}

d = defaultdict(dict)
step = 10
cont = True
while cont:
    lendf = len(df); print(lendf)
    for k,v in myfuncs.items():
        iters = 1
        t = 0
        while t < 0.2:
            ts = timeit.repeat(v, number=iters, repeat=3)
            t = min(ts)
            iters *= 10
        d[k][lendf] = t/iters
        if t > 2: cont = False
    df = pd.concat([df]*step)

pd.DataFrame(d).plot().legend(loc='upper center', bbox_to_anchor=(0.5, -0.15))
plt.yscale('log'); plt.xscale('log'); plt.ylabel('seconds'); plt.xlabel('df rows')
plt.show()
0
7

Это решение использует промежуточный шаг, сжимающий два столбца DataFrame до одного столбца, содержащего список значений. Это работает не только для строк, но и для всех типов столбцов.

import pandas as pd
df = pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']})
df['list']=df[['Year','quarter']].values.tolist()
df['period']=df['list'].apply(''.join)
print(df)

Результат:

   Year quarter        list  period
0  2014      q1  [2014, q1]  2014q1
1  2015      q2  [2015, q2]  2015q2
4
  • похоже, что другие типы не будут работать. Я получил TypeError: элемент последовательности 1: ожидаемый экземпляр str, найден floatPrometheus 10 апр '19 в 9:08
  • сначала примените приведение к строке. Операция соединения работает только для строкMarkus Dutschke 10 апр '19 в 10:58
  • Это решение не сработает для объединения двух столбцов с разными dtype, см. Мой ответ для правильного решения для такого случая. Good Will 16 мая '19 в 13:21
  • Вместо .apply(''.join)почему бы не использовать .str.join('')? Bill 28 мая в 0:45
6

Вот мое резюме приведенных выше решений для объединения / объединения двух столбцов со значениями int и str в новый столбец с использованием разделителя между значениями столбцов. Для этого работают три решения.

# be cautious about the separator, some symbols may cause "SyntaxError: EOL while scanning string literal".
# e.g. ";;" as separator would raise the SyntaxError

separator = "&&" 

# pd.Series.str.cat() method does not work to concatenate / combine two columns with int value and str value. This would raise "AttributeError: Can only use .cat accessor with a 'category' dtype"

df["period"] = df["Year"].map(str) + separator + df["quarter"]
df["period"] = df[['Year','quarter']].apply(lambda x : '{} && {}'.format(x[0],x[1]), axis=1)
df["period"] = df.apply(lambda x: f'{x["Year"]} && {x["quarter"]}', axis=1)
0
4

мое мнение ....

listofcols = ['col1','col2','col3']
df['combined_cols'] = ''

for column in listofcols:
    df['combined_cols'] = df['combined_cols'] + ' ' + df[column]
'''
1
  • 5
    Вы должны добавить пояснение к этому фрагменту кода. Добавление только кодовых ответов побуждает людей использовать код, который они не понимают, и не помогает им учиться. annedroiid 18 авг.
2

Как многие упоминали ранее, вы должны преобразовать каждый столбец в строку, а затем использовать оператор плюс для объединения двух строковых столбцов. Вы можете значительно улучшить производительность, используя NumPy.

%timeit df['Year'].values.astype(str) + df.quarter
71.1 ms ± 3.76 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit df['Year'].astype(str) + df['quarter']
565 ms ± 22.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
2
  • Я хотел бы использовать numpyified версию , но я получаю сообщение об ошибке: Input : df2['filename'] = df2['job_number'].values.astype(str) + '.' + df2['task_number'].values.astype(str)-> Вывод : TypeError: ufunc 'add' did not contain a loop with signature matching types dtype('<U21') dtype('<U21') dtype('<U21'). И job_number, и task_number - это целые числа. Karl Baker 03 мар.
  • Это потому, что вы объединяете два массива numpy. Это работает, если вы объедините массив numpy с серией pandas. какdf['Year'].values.astype(str) + df.quarterAbdulRehmanLiaqat 10 фев '20 в 11:23
1

Используйте .combine_first.

df['Period'] = df['Year'].combine_first(df['Quarter'])
1
  • Это не так. .combine_firstприведет либо к 'Year'сохранению значения 'Period', либо, если оно равно Null, к значению из 'Quarter'. Он не будет объединять две строки и сохранять их 'Period'. Steve G 29 янв.
1

Можно использовать метод assign DataFrame :

df= (pd.DataFrame({'Year': ['2014', '2015'], 'quarter': ['q1', 'q2']}).
  assign(period=lambda x: x.Year+x.quarter ))
0
def madd(x):
    """Performs element-wise string concatenation with multiple input arrays.

    Args:
        x: iterable of np.array.

    Returns: np.array.
    """
    for i, arr in enumerate(x):
        if type(arr.item(0)) is not str:
            x[i] = x[i].astype(str)
    return reduce(np.core.defchararray.add, x)

Например:

data = list(zip([2000]*4, ['q1', 'q2', 'q3', 'q4']))
df = pd.DataFrame(data=data, columns=['Year', 'quarter'])
df['period'] = madd([df[col].values for col in ['Year', 'quarter']])

df

    Year    quarter period
0   2000    q1  2000q1
1   2000    q2  2000q2
2   2000    q3  2000q3
3   2000    q4  2000q4