#python #pandas
#python #панды
Вопрос:
У меня есть фрейм данных 2xN сообщений чата, и я пытаюсь найти самый чистый способ объединения последовательных сообщений, исходящих от одного и того же говорящего. Вот пример данных, с которыми я работаю:
mydata = pd.DataFrame(data=[['A','random text'],
['B','random text'],
['A','random text'],
['A','random text'],
['A','random text'],
['B','random text'],
['A','random text'],
['B','random text'],
['B','random text'],
['A','random text']], columns=['speaker','message'])
Надеюсь, вы видите, что порядок динамиков не в формате ABAB, как хотелось бы. Вместо этого есть несколько последовательностей AAAB и ABBA. В настоящее время я думаю перестроить фрейм данных с нуля, сверяя идентификатор каждой строки с идентификатором следующей позиции индекса…
mergeCheck = True
while mergeCheck is True:
# set length of the dataframe
lenDF = len(mydata)
# empty list to rebuild dataframe
mergeDF = []
# set index position at the beginning of dataframe
i = 0
while i < lenDF-1:
# check whether adjacent rows have different ID
if mydata['speaker'].iloc[i] != mydata['speaker'].iloc[i 1]:
# if true, append row as is to mergeDF list
mergeDF.append([mydata['speaker'].iloc[i],
mydata['message'].iloc[i]])
# increase index position by 1
i =1
else:
# merge messages
mergeDF.append([mydata['speaker'].iloc[i],
mydata['message'].iloc[i] mydata['message'].iloc[i 1]])
# increase index position by 2
i =2
# exit the loop if index position falls on the last message
if i == lenDF-1:
# if true, append row as is to mergeDF list
mergeDF.append([mydata['speaker'].iloc[i],
mydata['message'].iloc[i]])
# increase counter by 1
i =1
if i == lenDF:
mergeCheck = False
Однако это работает только для двух смежных сообщений. Возвращаясь к моим исходным данным, при вводе в фрейм данных приведенная выше функция генерирует следующий вывод…
--------------------------
speaker | message
--------------------------
A 'random text'
B 'random text'
A 'random textrandom text'
A 'random text'
B 'random text'
A 'random text'
B 'random textrandom text'
A 'random text'
--------------------------
Я подумал расширить функцию, чтобы проверить больше сравнений i (т. Е. Выполняет ‘.iloc [i] != .iloc [i 2]’ или ‘.iloc [i] ! = .iloc [i 3]’ и т.д.), Но это становится неработоспособным очень быстро. Я думаю, мне нужен какой-то способ повторить описанную выше функцию, пока фрейм данных не будет в желаемом формате. Но я не уверен, как это сделать.
Ответ №1:
Возможное решение заключается в следующем:
df1 = mydata[mydata['speaker']=='A'].reset_index()
df2= mydata[mydata['speaker']=='B'].reset_index()
df = pd.concat([df1, df2]).sort_index()
который возвращает
index speaker message
0 0 A random text
0 1 B random text
1 2 A random text
1 5 B random text
2 3 A random text
2 7 B random text
3 4 A random text
3 8 B random text
4 6 A random text
5 9 A random tex
если у вас есть временная метка для них, не забудьте отсортировать по времени / дате перед сбросом индекса. Кроме того, при объединении остерегайтесь времени.
Редактировать
После ваших разъяснений в комментариях я предлагаю это. Сначала создайте ключ, который соответствует равным объектам (A, B), а затем сгруппируйте по динамикам и объектам (ключам)
df['key'] = (df['speaker'] != df['speaker'].shift(1)).astype(int).cumsum()
что дает
speaker message key
0 A random text 1
1 B random text 2
2 A random text 3
3 A random text 3
4 A random text 3
5 B random text 4
6 A random text 5
7 B random text 6
8 B random text 6
9 A random text 7
Теперь вам просто нужно groupby
df = df.groupby(['key', 'speaker'])['message'].apply(' '.join)
df
что дает
key speaker
1 A random text
2 B random text
3 A random text random text random text
4 B random text
5 A random text
6 B random text random text
7 A random text
Комментарии:
1. Спасибо за ввод — К сожалению, это не объединяет столбцы сообщений вместе. Я, вероятно, должен был прояснить это в исходном сообщении!
2. Ах! Теперь это имеет смысл. Я отредактировал свой ответ.
3. Спасибо, это здорово и намного короче, чем мой подход!
Ответ №2:
После некоторого изучения я нашел лучшее решение, чем мой OP. Я подробно расскажу об этом здесь для всех, кто сталкивается с подобной проблемой. Я пока воздержусь от принятия своего собственного ответа на случай, если кто-то предложит лучший вариант.
# compare each row with the previous
mydata['prev_speaker'] = mydata['speaker'].shift(1).mask(pd.isnull, mydata['speaker'])
# boolean value to determine whether current speaker differs from previous
mydata['speaker_change'] = np.where(mydata['speaker'] != mydata['prev_speaker'], 'True','False')
# empty list to record changes in speaker
counterList = []
# initialize a counter to loop through dataframe
counter =1
# loop through dataframe, increasing counter by 1 if the speaker changes
for row in mydata['speaker_change']:
if row == 'False':
counterList.append(counter)
else:
counter =1
counterList.append(counter)
# add counterList to dataframe
mydata['chunking'] = counterList
# group the original message based on the chunking variable
mydata['message'] = mydata.groupby(['chunking'])['message'].transform(lambda x: ' '.join(x))
# drop duplicate rows based on message content and chunking
mydata = mydata.drop_duplicates(subset=['message','chunking'])
# drop non-needed columns
mydata = mydata.drop(['prev_speaker','speaker_change','chunking'], axis=1)
Что теперь дает мне следующее:
|---------------------|-------------------------------------|
| Speaker | Message |
|---------------------|-------------------------------------|
| A | random text |
|---------------------|-------------------------------------|
| B | random text |
|---------------------|-------------------------------------|
| A | random text random text random text |
|---------------------|-------------------------------------|
| B | random text |
|---------------------|-------------------------------------|
| A | random text |
|---------------------|-------------------------------------|
| B | random text random text |
|---------------------|-------------------------------------|
| A | random text |
|---------------------|-------------------------------------|