Панды: объединить фрейм данных, но суммировать перекрывающиеся столбцы - PullRequest
0 голосов
/ 11 декабря 2018

Я читал много сообщений о merge() и join() методах pandas.DataFrames и пробовал их на своей собственной проблеме, но не нашел решения.

У меня оченьбольшой файл данных (.csv), содержащий почасовое потребление чего-либо для различных идентификаторов.Я хочу агрегировать потребление для каждого идентификатора за каждый месяц.

Из-за ограничений памяти мне нужно обработать файл почасового потребления с read_csv порциями (используя опцию chunk_size) и получитьзагрузка DataFrames потребления для идентификаторов за несколько месяцев, например:

df1 = 
 Month  Dec  Nov
ID             
XXX    4.0  1.0
YYY    8.0  3.0
ZZZ    4.0  1.0 

df2 = 
 Month  Dec  Nov  Oct
ID                  
AAA    1.0  7.0  9.0
BBB    0.0  NaN  2.0
YYY    5.0  5.0  0.0

Генерируется для этого поста с помощью:

df1 = pd.DataFrame({'ID': ['XXX','XXX','YYY','YYY','ZZZ','ZZZ'], 
                    'Month': ['Nov','Dec']*3, 
                    'Consumption': [1.0,4.0,3.0,8.0,1.0,4.0]})
df1 = df1.pivot(index='ID', columns='Month', values='Consumption')
df2 = pd.DataFrame({'ID': ['AAA','AAA','AAA','YYY','YYY','YYY','BBB','BBB','BBB'], 
                    'Month': ['Oct','Nov','Dec']*3, 
                    'Consumption': [9,7,1,0,5,5,2,np.nan,0]})
df2 = df2.pivot(index='ID', columns='Month', values='Consumption')

Обратите внимание, что существует разница между потреблением0.0 и NaN.0.0 означает, что по крайней мере один показатель потребления составил 0.0 в месяц, но NaN означает, что вообще не было зарегистрировано никакого значения потребления, и в этом случае 0 не может быть принято.Для моих целей это различие должно быть разным.

Поскольку файл данных обрабатывается порциями, есть несколько идентификаторов, которые появляются в нескольких кадрах данных, например, YYY, и для этих идентификаторовиногда месяцы тоже перекрываются, например Nov для ID YYY.В этом случае потребление в первой половине месяца составляет df1, а во второй половине - df2.

Поэтому для агрегирования потребления мне необходимо объединить эти фреймы данных по 'ID' и суммезначения в перекрывающихся 'Месяцах'.

Прямое суммирование DataFrames дает много NaN:

df1 + df2 = 
 Month   Dec  Nov  Oct
ID                   
AAA     NaN  NaN  NaN
BBB     NaN  NaN  NaN
XXX     NaN  NaN  NaN
YYY    13.0  8.0  NaN
ZZZ     NaN  NaN  NaN

Я предполагаю, что это потому, что при суммировании идентификаторов / месяцев df1которые не отображаются df2, возвращает NaN.

Внешнее объединение создает суффиксированные столбцы для перекрывающихся месяцев:

df1.merge(df2,how='outer',on='ID') = 
 Month  Dec_x  Nov_x  Dec_y  Nov_y  Oct
ID                                    
XXX      4.0    1.0    NaN    NaN  NaN
YYY      8.0    3.0    5.0    5.0  0.0
ZZZ      4.0    1.0    NaN    NaN  NaN
AAA      NaN    NaN    1.0    7.0  9.0
BBB      NaN    NaN    0.0    NaN  2.0

Я не смог получить combine_first делать то, что я хочу либо.

Что-то, что я хочу, это что-то посередине, это выглядит так:

 Month   Dec  Nov  Oct
ID                   
XXX      4.0  1.0  NaN
YYY     13.0  8.0  0.0
ZZZ      4.0  1.0  NaN
AAA      1.0  7.0  9.0
BBB      0.0  NaN  2.0

Где перекрывающиеся месяцы суммируются так, что x + NaN = x, NaN + y = y и NaN + NaN = NaN.


Одно решение, которое я вижу, чтобы выполнить слияние, а затем суммировать перекрывающиеся столбцы, игнорируя NaN:

df3 = df1.merge(df2,how='outer',on='ID',suffixes=['','_x'])
overlapping_months_sufx = df3.columns.values[df3.columns.str.endswith('_x')]
for mnth_sufx in overlapping_months_sufx:
    mnth = mnth_sufx[:-2]
    df3[mnth][df3[mnth_sufx].notnull()] = df3[mnth].fillna(0) + df3[mnth_sufx]
    df3=df3.drop(columns=mnth_sufx)
df3 = 
 Month   Dec  Nov  Oct
ID                   
XXX     4.0  1.0  NaN
YYY    13.0  8.0  0.0
ZZZ     4.0  1.0  NaN
AAA     1.0  7.0  9.0
BBB     0.0  NaN  2.0

Учитывая размер этого набора данных, было бы здорово иметь наиболее эффективный способ агрегировать все это.Есть ли лучший способ сделать это, возможно, за один шаг?

Спасибо, Крис

Ответы [ 2 ]

0 голосов
/ 11 декабря 2018

Мой подход заключается в том, чтобы вставлять те значения, которые впоследствии находятся только в одном кадре данных, в тех индексах, где результатом суммы является NaN:

result = df1 + df2

Month   Dec  Nov  Oct
ID                   
AAA     NaN  NaN  NaN
BBB     NaN  NaN  NaN
XXX     NaN  NaN  NaN
YYY    13.0  8.0  NaN
ZZZ     NaN  NaN  NaN

result = result.where(~result.isna(), df1)

Month   Dec  Nov  Oct
ID                   
AAA     NaN  NaN  NaN
BBB     NaN  NaN  NaN
XXX     4.0  1.0  NaN
YYY    13.0  8.0  NaN
ZZZ     4.0  1.0  NaN

result = result.where(~result.isna(), df2)

Month   Dec  Nov  Oct
ID                   
AAA     1.0  7.0  9.0
BBB     0.0  NaN  2.0
XXX     4.0  1.0  NaN
YYY    13.0  8.0  0.0
ZZZ     4.0  1.0  NaN
0 голосов
/ 11 декабря 2018

Вот попытка.Пожалуйста, оставьте комментарий, если я правильно понял.

Дано:

>>> df1                                                                                                                
Month  Dec  Nov
ID             
XXX    4.0  1.0
YYY    8.0  3.0
ZZZ    4.0  1.0
>>> df2                                                                                                                
Month  Dec  Nov  Oct
ID                  
AAA    1.0  7.0  9.0
BBB    0.0  NaN  2.0
YYY    5.0  5.0  0.0

Решение:

>>> pd.concat([df1, df2]).reset_index().groupby('ID', sort=False).sum(min_count=1)
      Dec  Nov  Oct
ID                 
XXX   4.0  1.0  NaN
YYY  13.0  8.0  0.0
ZZZ   4.0  1.0  NaN
AAA   1.0  7.0  9.0
BBB   0.0  NaN  2.0

Объяснение:

Конкатенация простоставит df2 в df1.

>>> cat = pd.concat([df1, df2])                                                                                        
>>> cat                                                                                                                
     Dec  Nov  Oct
ID                
XXX  4.0  1.0  NaN
YYY  8.0  3.0  NaN
ZZZ  4.0  1.0  NaN
AAA  1.0  7.0  9.0
BBB  0.0  NaN  2.0
YYY  5.0  5.0  0.0

reset_index перемещает индекс в столбцы.

>>> cat = cat.reset_index()                                                                                            
>>> cat                                                                                                                
    ID  Dec  Nov  Oct
0  XXX  4.0  1.0  NaN
1  YYY  8.0  3.0  NaN
2  ZZZ  4.0  1.0  NaN
3  AAA  1.0  7.0  9.0
4  BBB  0.0  NaN  2.0
5  YYY  5.0  5.0  0.0

Я сделал это так, что есть столбец с именем 'ID', по которому я могу сгруппировать другие значения.groupby('ID', sort=False) создает группы строк с одинаковым значением в столбце 'ID'sort=False гарантирует, что строки в конечном результате не будут отсортированы в соответствии с вашим выводом).

Мы можем проверить группуразмеры как этот:

>>> cat.groupby('ID', sort=False).size()                                                                               
ID
XXX    1
YYY    2
ZZZ    1
AAA    1
BBB    1
dtype: int64

Как вы можете видеть, у нас есть только одна группа размером два, потому что 'YYY' ID является единственной дублированной.

sum(min_count=1) работает так:значения в каждой группе суммируются относительно их столбца.Параметр min_count=1 гарантирует, что последовательность всех значений NaN приведет к сумме NaN при суммировании.

>>> cat.groupby('ID', sort=False).sum(min_count=1)                                                      
      Dec  Nov  Oct
ID                 
XXX   4.0  1.0  NaN
YYY  13.0  8.0  0.0
ZZZ   4.0  1.0  NaN
AAA   1.0  7.0  9.0
BBB   0.0  NaN  2.0

Демонстрация для min_count:

>>> s = pd.Series([np.nan, np.nan])                                                                                    
>>> s                                                                                                                  
0   NaN
1   NaN
dtype: float64
>>>                                                                                                                    
>>> s.sum()                                                                                                            
0.0
>>> s.sum(min_count=1)                                                                                                 
nan
>>> s[0] = 1                                                                                                           
>>> s                                                                                                                  
0    1.0
1    NaN
dtype: float64
>>> s.sum()                                                                                                            
1.0
>>> s.sum(min_count=1)                                                                                                 
1.0
>>> s.sum(min_count=2)                                                                                                 
nan
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...