Объедините два столбца текста в dataframe в pandas / python


488

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

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

Ответы:


532

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

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

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

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

Остерегайтесь NaNs, делая это!


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

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

Где "-" - это разделитель.


13
Можно ли добавить несколько столбцов вместе, не печатая все столбцы? Скажем add(dataframe.iloc[:, 0:10])для примера?
Гейзенберг

5
@ Heisenberg Это должно быть возможно с помощью встроенного Python sum.
Сильвадо

6
@silvado Не могли бы вы привести пример добавления нескольких столбцов? Спасибо
c1c1c1

6
Будьте осторожны, вам нужно применить карту (str) ко всем столбцам, которые не являются строками. если четверть - это число, которое вы делаете, dataframe["period"] = dataframe["Year"].map(str) + dataframe["quarter"].map(str)карта просто применяет преобразование строк ко всем записям.
Озгур

13
Это решение может создать проблемы, если у вас есть нан значения,

269
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 () здесь


20
lambda x: ''.join(x)просто ''.joinнет?
DSM

6
@OzgurOzturk: ​​дело в том, что лямбда-часть lambda x: ''.join(x)конструкции ничего не делает; это как использовать lambda x: sum(x)вместо просто sum.
DSM

4
Подтверждено же результат при использовании ''.join, а именно: df['period'] = df[['Year', 'quarter']].apply(''.join, axis=1).
Макс Генис

1
@Archie joinпринимает только strэкземпляры в итерации. Используйте, mapчтобы преобразовать их всех в strи затем использовать join.
Джон Струд

16
'-'. join (x.map (str))
Манджул

257

Небольшие наборы данных (<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


Какая разница между 261 и 264 в вашем времени?
Антон Протопопов

@AntonProtopopov, очевидно, 100 мс из ниоткуда :)
Денис Голомазов

@AntonProtopopov, я думаю, это смесь двух моментов времени: один использовал оптимизацию CPU / GPU, другой - нет. Я обновил свой ответ и поместил там оба набора времени ...
MaxU

Такое использование .sum () завершается неудачно, если все столбцы выглядят так, как будто они могут быть целыми числами (то есть являются строковыми формами целых чисел). Вместо этого кажется, что панды преобразуют их обратно в числовые значения перед суммированием!
CPBL

@CPBL, попробуйте этот подход:df.T.apply(lambda x: x.str.cat(sep=''))
MaxU

157

Для 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.


12
Это кажется намного лучше (возможно, и более эффективным), чем lambdaили map; также это только читает наиболее чисто.
Двандерсон

1
@ZakS, передавая оставшиеся столбцы как фрейм данных вместо ряда в качестве первого параметра str.cat(). Я
исправлю

Какую версию панд вы используете? Я получаю ValueError: Вы хотели указать sepключевое слово? в пандах - 0,23,4. Спасибо!
Циньцин Лю

@QinqingLiu, я перепроверил их с pandas-0.23.4, и они, кажется, работают. sepПараметр необходим только если вы намерены отделить части сцепленной строки. Если вы получили ошибку, пожалуйста, покажите нам ваш неудачный пример.
LeoRochael

31

Использование функции 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
Гораздо быстрее: .apply (''. Join (x), axis = 1)
Ганем,

19

Простой ответ на ваш вопрос.

    year    quarter
0   2000    q1
1   2000    q2

> df['year_quarter'] = df['year'] + '' + df['quarter']

> print(df['year_quarter'])
  2000q1
  2000q2

3
потерпит неудачу, если Yearне строка
geher

4
использованиеdf['Year'].astype(str) + '' + df['quarter'].astype(str)
Yedhrab

2
В чем именно смысл этого решения, поскольку оно совпадает с верхним ответом?
AMC

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

12

Давайте предположим , что ваш dataframeIS 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']

3
Указано в виде строкиdf['Period'] = df['Year'].map(str) + df['Quarter'].map(str)
Stuber

Я получаю, 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).
Карл Бейкер

Тем df2['filename'] = df2['job_number'].astype(str) + '.' + df2['task_number'].astype(str)не менее, сделал работу.
Карл Бейкер

@KarlBaker, я думаю, что у вас не было строк на входе. Но я рад, что вы поняли это. Если вы посмотрите на пример, dataframeкоторый я создал выше, вы увидите, что все столбцы strings.
Самуэль Нде

В чем именно смысл этого решения, поскольку оно совпадает с верхним ответом?
AMC

10

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

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

К вашему сведению: этот метод прекрасно работает с Python 3, но доставляет мне проблемы с Python 2.
Алекс П. Миллер


9

более эффективным является

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 Аккуратное решение, это также позволяет нам указать столбцы: например, df.values[:, 0:3]или df.values[:, [0,2]].
Снег овсянка

9

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

columns = ['whatever', 'columns', 'you', 'choose']
df['period'] = df[columns].astype(str).sum(axis=1)

Выглядит круто, но что, если я хочу добавить разделитель между строками, например '-'?
Odisseo

@Odisseo посмотри этот ответ stackoverflow.com/questions/19377969/…
geher

6

Использование 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()

6

Самое простое решение:

Общее решение

df['combined_col'] = df[['col1', 'col2']].astype(str).apply('-'.join, axis=1)

Вопрос конкретное решение

df['quarter_year'] = df[['quarter', 'year']].astype(str).apply(''.join, axis=1)

Укажите предпочитаемый разделитель в кавычках перед .join


Разве это не идентично более старому, более популярному ответу ?
AMC

5

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

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

похоже, другие dtypes не будут работать. Я получил сообщение об ошибке TypeError: элемент последовательности 1: ожидаемый экземпляр str, обнаружение с плавающей точкой
Prometheus,

примените сначала приведение к строке. Операция объединения работает только для строк
Маркус Дучке

Это решение не будет работать для объединения двух столбцов с различным dtype, см. Мой ответ для правильного решения для такого случая.
Доброй воли

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)

Я хотел бы использовать 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 являются целыми числами.
Карл Бейкер

Это потому, что вы комбинируете два массива. Это работает, если вы объединяете массив numpy с сериями панд. asdf['Year'].values.astype(str) + df.quarter
AbdulRehmanLiaqat

2

Я думаю, что лучший способ объединить столбцы в пандах - это преобразовать оба столбца в целое, а затем в str.

df[['Year', 'quarter']] = df[['Year', 'quarter']].astype(int).astype(str)
df['Period']= df['Year'] + 'q' + df['quarter']

преобразование обоих столбцов в целое число Зачем сначала преобразовывать в int? Как только вы удалите эту странность, это решение идентично текущему топ-ответу.
AMC

2

Вот мое резюме вышеупомянутых решений, чтобы объединить / объединить два столбца со значениями 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)

Спасибо! Ваше решение f-string было именно тем, что я надеялся найти !!!
leerssej

1

Использование .combine_first.

df['Period'] = df['Year'].combine_first(df['Quarter'])

Это не правильно. .combine_firstприведет к тому, что значение 'Year'будет сохранено в 'Period', или, если оно равно Null, значение из 'Quarter'. Он не объединит две строки и не сохранит их 'Period'.
Стив Дж

Это категорически неправильно.
AMC

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

0

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

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

-1
dataframe["period"] = dataframe["Year"].astype(str).add(dataframe["quarter"])

или если значения похожи на [2000] [4] и хотите сделать [2000q4]

dataframe["period"] = dataframe["Year"].astype(str).add('q').add(dataframe["quarter"]).astype(str)

подставляя .astype(str)с .map(str)работой тоже.


Это по сути идентично верхнему ответу.
AMC
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.