У меня есть фрейм данных 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]' д.), но это очень быстро становится неработоспособным. Я думаю, что мне нужно каким-то образом повторить вышеуказанную функцию, пока фрейм данных не будет в нужном формате. Но я не уверен, как это сделать.
Возможное решение таково:
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
Теперь вам просто нужно сгруппировать
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
Ах! Теперь это имеет смысл. Я отредактировал свой ответ.
Спасибо, это здорово, и намного короче, чем мой подход!
После некоторого изучения я нашел лучшее решение, чем мой ОП. Я подробно расскажу об этом здесь для тех, кто испытывает подобную проблему. Я пока воздержусь от принятия своего собственного ответа на случай, если кто-то предложит лучший вариант.
# 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 |
|---------------------|-------------------------------------|
Спасибо за вклад. К сожалению, это не объединяет столбцы сообщений. Я, вероятно, должен был сделать это более ясным в исходном посте!