Поскольку я не уверен в точной терминологии - скажем, у меня есть этот файл:
dataA.csv:
event,car,bike,bus
63175,,18,
65641,45,9,
65805,,,54
68388,,65,
68388,,,39
73041,7,,18
79336,,44,
79423,,,5
Считая это с помощью dataA = pd.read_csv("dataA.csv", dtype='Int64')
, мы получаем a pandas DataFrame:
dataA:
event car bike bus
0 63175 <NA> 18 <NA>
1 65641 45 9 <NA>
2 65805 <NA> <NA> 54
3 68388 <NA> 65 <NA>
4 68388 <NA> <NA> 39
5 73041 7 <NA> 18
6 79336 <NA> 44 <NA>
7 79423 <NA> <NA> 5
В двух строках столбец «событие» имеет одинаковое значение (то, что я называю «дубликатами»):
3 68388 <NA> 65 <NA>
4 68388 <NA> <NA> 39
... и я бы хотел, чтобы они были «сжаты» (это правильное слово?) в одну строку, так что вместо NaN (то есть, NA) есть фактические значения (где это возможно):
3 68388 <NA> 65 39
От Как сжать объединение двух Pandas Данных с NaN и дублирующихся ключей соединения? Я получил ответ, что должен использовать .groupby(...).first()
- и действительно, это работает; этот сценарий:
#!/usr/bin/env python3
import pandas as pd
print(pd.__version__) # 1.0.2 for me
dataA = pd.read_csv("dataA.csv", dtype='Int64')
print("dataA:")
print(dataA)
# make sure Pandas prints entirety of DataFrame
pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option('display.width', None)
pd.set_option('display.max_colwidth', None)
dataCompact = dataA.groupby('event').first() ##***
print("\ndataCompact:")
print(dataCompact)
в конечном итоге печатает:
dataCompact:
car bike bus
event
63175 <NA> 18 <NA>
65641 45 9 <NA>
65805 <NA> <NA> 54
68388 <NA> 65 39
73041 7 <NA> 18
79336 <NA> 44 <NA>
79423 <NA> <NA> 5
... что я и хотел, так что это работает.
Однако, при ближайшем рассмотрении, Я понял, что на самом деле не понимаю, как это работает, то есть я не могу точно сказать, на что конкретно ссылается .first()
в данном случае; и https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.first.html мне не очень помогает, потому что в нем говорится " Метод для подстановки начальных периодов данных временных рядов на основе смещения даты. " (и большинство других вводных страниц в Интернете следуют пример дат), - но здесь я не использую даты.
Итак, я провел несколько экспериментов - в основном изменил строку, отмеченную ##***
в приведенном выше коде, и посмотрел на распечатки.
Во-первых, если я вместо этого использую эту строку:
dataCompact = dataA.groupby('event').apply(lambda x: "{} ({}): {}".format(type(x), len(x), x.values.tolist())) ##***
... Я получаю эту распечатку:
dataCompact:
event
63175 <class 'pandas.core.frame.DataFrame'> (1): [[63175, <NA>, 18, <NA>]]
65641 <class 'pandas.core.frame.DataFrame'> (1): [[65641, 45, 9, <NA>]]
65805 <class 'pandas.core.frame.DataFrame'> (1): [[65805, <NA>, <NA>, 54]]
68388 <class 'pandas.core.frame.DataFrame'> (2): [[68388, <NA>, 65, <NA>], [68388, <NA>, <NA>, 39]]
73041 <class 'pandas.core.frame.DataFrame'> (1): [[73041, 7, <NA>, 18]]
79336 <class 'pandas.core.frame.DataFrame'> (1): [[79336, <NA>, 44, <NA>]]
79423 <class 'pandas.core.frame.DataFrame'> (1): [[79423, <NA>, <NA>, 5]]
dtype: object
Отсюда я понимаю, что, по сути, groupby('event')
обеспечивает DataFrame для каждого уникального значения столбца 'event':
- , если значение уникально в исходном наборе данных, этот DataFrame имеет одну строку; однако
- если это «дублирующее» значение, мы получаем DataFrame с двумя строками (или столько же строк, сколько имеется «дубликатов» этого конкретного значения).
Таким образом, .first()
должен принять DataFrame с N> = 1 строками в качестве входных данных и вернуть одну строку.
Однако здесь начинается мое замешательство - я бы прочитал .first()
, чтобы сослаться на возвращение первые из N> = 1 входных строк; но в этом случае значения не были бы «сжаты» (то есть слоты с действительными номерами заменяют слоты на <NA>
(неопределенные значения)); - вместо этого все остальные строки, кроме первого, будут удалены! И это не то, что здесь происходит ...
Итак, я попытался смоделировать то, что делает .first()
, написав свой собственный лямбда-обработчик для .apply()
:
def proc_df_first(x):
# here we get a DataFrame with single row, if "event" (groupby arg) is a unique value;
# or a DataFrame with as many rows, as there are repeated rows with "event" of same value ("duplicate")
if len(x) == 1:
return x
elif len(x) > 1:
# create empty return DataFrame (eventually it will only have a single row)
retdf = pd.DataFrame(columns = x.columns)
#return retdf # is empty, so is skipped in final result of .groupby.apply
# must populate rowdata first, then assign via .loc (SO:17091769)
for icol in x.columns:
coldata = x[icol] # is Series
thisval = pd.NA # initialize the "compact" single value we want to set for this column (eventually derived from all the row values in this column)
for idx, val in coldata.iteritems():
#print("***", idx, val, val is pd.NA) # `val is None` is always False; `val==pd.NA` always prints `<NA>`; `val is pd.NA` is correct
if thisval is pd.NA:
if val is not pd.NA:
# found the first non-NA value; save it, and stop looking further
thisval = val
break
# store saved non-NA value into return DataFrame
retdf.at[ x.index[0], icol ] = thisval # SO: 13842088
return retdf
dataCompact = dataA.groupby('event').apply(lambda x: proc_df_first(x)) ##***
.. ... который в итоге печатает:
dataCompact:
event car bike bus
event
63175 0 63175 <NA> 18 <NA>
65641 1 65641 45 9 <NA>
65805 2 65805 <NA> <NA> 54
68388 3 68388 NaN 65 39
73041 5 73041 7 <NA> 18
79336 6 79336 <NA> 44 <NA>
79423 7 79423 <NA> <NA> 5
... что по сути тот же результат, что и .groupby('event').first()
(кроме дублированного столбца "события" (это будет иерархическая метка?) и индекса столбец).
Итак, вот мои вопросы:
- В соответствии с вышесказанным, я бы сказал, что
.first()
возвращает первое не-NA значение для каждого столбца в набор строк, приводящий к однорядному представлению набора строк - это правильно? - Почему, из-за громкого крика, я получил NaN в столбце "car" вывода для события " "== 68388 - когда я попытался обработать все внутренне как" Int64 "(с соответствующим значением
pd.NA
)? Я знаю, что могу сделать dataA.groupby('event').apply(lambda x: proc_df_first(x)).astype('Int64')
и получить pd.NA
везде - но, учитывая, что я перебираю всю таблицу элемент за элементом, я бы не хотел, чтобы еще одна l oop по таблице - просто чтобы получить избавиться от одного поплавка NaN. Что я мог сделать в своем обработчике, чтобы гарантировать, что возврат из proc_df_first()
в .apply()
всегда будет иметь значения pd.NA
, если это конкретное значение не определено?