Работа с отсутствующими данными
В этом разделе мы обсудим отсутствующие (также обозначаемые как NA) значения в pandas.
Примечание
Выбор использования NaN для обозначения отсутствующих данных в основном обусловлен простотой и производительностью. Начиная с pandas 1.0, некоторые необязательные типы данных начинают экспериментировать с родным NA скаляром, используя подход на основе маски. Более подробно см. здесь.
См. пособие для некоторых продвинутых стратегий.
Значения, считающиеся «отсутствующими»
Поскольку данные поступают в самых разнообразных формах, pandas стремится быть гибким в отношении обработки отсутствующих данных. Хотя NaN является стандартным маркером отсутствующего значения по соображениям вычислительной скорости и удобства, нам необходимо легко определять это значение с данными разных типов: с плавающей точкой, целыми числами, булевыми значениями и общими объектами. Однако во многих случаях возникает Python None, и мы хотим также считать это «отсутствующим» или «недоступным» или «NA».
Примечание
Если вы хотите считать inf и -inf «NA» в вычислениях, вы можете установить pandas.options.mode.use_inf_as_na = True.
In [1]: df = pd.DataFrame(
...: np.random.randn(5, 3),
...: index=["a", "c", "e", "f", "h"],
...: columns=["one", "two", "three"],
...: )
...:
In [2]: df["four"] = "bar"
In [3]: df["five"] = df["one"] > 0
In [4]: df
Out[4]:
one two three four five
a 0.469112 -0.282863 -1.509059 bar True
c -1.135632 1.212112 -0.173215 bar False
e 0.119209 -1.044236 -0.861849 bar True
f -2.104569 -0.494929 1.071804 bar False
h 0.721555 -0.706771 -1.039575 bar True
In [5]: df2 = df.reindex(["a", "b", "c", "d", "e", "f", "g", "h"])
In [6]: df2
Out[6]:
one two three four five
a 0.469112 -0.282863 -1.509059 bar True
b NaN NaN NaN NaN NaN
c -1.135632 1.212112 -0.173215 bar False
d NaN NaN NaN NaN NaN
e 0.119209 -1.044236 -0.861849 bar True
f -2.104569 -0.494929 1.071804 bar False
g NaN NaN NaN NaN NaN
h 0.721555 -0.706771 -1.039575 bar True
Для упрощения (и работы с различными типами массивов) определения отсутствующих значений pandas предоставляет функции isna() и notna(), которые также являются методами объектов Series и DataFrame:
In [7]: df2["one"]
Out[7]:
a 0.469112
b NaN
c -1.135632
d NaN
e 0.119209
f -2.104569
g NaN
h 0.721555
Name: one, dtype: float64
In [8]: pd.isna(df2["one"])
Out[8]:
a False
b True
c False
d True
e False
f False
g True
h False
Name: one, dtype: bool
In [9]: df2["four"].notna()
Out[9]:
a True
b False
c True
d False
e True
f True
g False
h True
Name: four, dtype: bool
In [10]: df2.isna()
Out[10]:
one two three four five
a False False False False False
b True True True True True
c False False False False False
d True True True True True
e False False False False False
f False False False False False
g True True True True True
h False False False False False
Предупреждение
Следует помнить, что в Python (и NumPy) nan's не сравниваются как равные, но None's сравниваются как равные. Обратите внимание, что pandas/NumPy использует тот факт, что np.nan != np.nan, и рассматривает None как np.nan.
In [11]: None == None # noqa: E711
Out[11]: True
In [12]: np.nan == np.nan
Out[12]: False
Таким образом, по сравнению с вышеизложенным, скалярное сравнение на равенство по сравнению с None/np.nan не предоставляет полезной информации.
In [13]: df2["one"] == np.nan
Out[13]:
a False
b False
c False
d False
e False
f False
g False
h False
Name: one, dtype: bool
Целочисленные типы данных и отсутствующие данные
Поскольку NaN является числом с плавающей точкой, столбец целых чисел с одним или несколькими отсутствующими значениями преобразуется в тип данных с плавающей точкой (см. Поддержка целочисленных NA для получения дополнительной информации). pandas предоставляет массив с допускаемыми целыми числами, который можно использовать, явно запросив тип данных:
In [14]: pd.Series([1, 2, np.nan, 4], dtype=pd.Int64Dtype())
Out[14]:
0 1
1 2
2 <NA>
3 4
dtype: Int64
В качестве альтернативы можно использовать строковый псевдоним dtype='Int64' (обратите внимание на заглавную букву "I").
См. Тип данных целочисленных данных с допускаемыми значениями для получения дополнительной информации.
Даты и время
Для типов datetime64[ns] NaT представляет собой отсутствующие значения. Это псевдородное значение-маркер, которое может быть представлено NumPy в одном типе данных (datetime64[ns]). Объекты pandas обеспечивают совместимость между NaT и NaN.
In [15]: df2 = df.copy()
In [16]: df2["timestamp"] = pd.Timestamp("20120101")
In [17]: df2
Out[17]:
one two three four five timestamp
a 0.469112 -0.282863 -1.509059 bar True 2012-01-01
c -1.135632 1.212112 -0.173215 bar False 2012-01-01
e 0.119209 -1.044236 -0.861849 bar True 2012-01-01
f -2.104569 -0.494929 1.071804 bar False 2012-01-01
h 0.721555 -0.706771 -1.039575 bar True 2012-01-01
In [18]: df2.loc[["a", "c", "h"], ["one", "timestamp"]] = np.nan
In [19]: df2
Out[19]:
one two three four five timestamp
a NaN -0.282863 -1.509059 bar True NaT
c NaN 1.212112 -0.173215 bar False NaT
e 0.119209 -1.044236 -0.861849 bar True 2012-01-01
f -2.104569 -0.494929 1.071804 bar False 2012-01-01
h NaN -0.706771 -1.039575 bar True NaT
In [20]: df2.dtypes.value_counts()
Out[20]:
float64 3
object 1
bool 1
datetime64[ns] 1
dtype: int64
Вставка отсутствующих данных
Вы можете вставить отсутствующие значения, просто присвоив значения контейнерам. Фактическое используемое отсутствующее значение будет выбрано на основе типа данных.
Например, числовые контейнеры всегда будут использовать NaN независимо от выбранного типа отсутствующего значения:
In [21]: s = pd.Series([1, 2, 3])
In [22]: s.loc[0] = None
In [23]: s
Out[23]:
0 NaN
1 2.0
2 3.0
dtype: float64
Аналогично, контейнеры дат и времени всегда будут использовать NaT.
Для контейнеров объектов pandas будет использовать заданное значение:
In [24]: s = pd.Series(["a", "b", "c"])
In [25]: s.loc[0] = None
In [26]: s.loc[1] = np.nan
In [27]: s
Out[27]:
0 None
1 NaN
2 c
dtype: object
Вычисления с отсутствующими данными
Отсутствующие значения естественным образом распространяются через арифметические операции между объектами pandas.
In [28]: a
Out[28]:
one two
a NaN -0.282863
c NaN 1.212112
e 0.119209 -1.044236
f -2.104569 -0.494929
h -2.104569 -0.706771
In [29]: b
Out[29]:
one two three
a NaN -0.282863 -1.509059
c NaN 1.212112 -0.173215
e 0.119209 -1.044236 -0.861849
f -2.104569 -0.494929 1.071804
h NaN -0.706771 -1.039575
In [30]: a + b
Out[30]:
one three two
a NaN NaN -0.565727
c NaN NaN 2.424224
e 0.238417 NaN -2.088472
f -4.209138 NaN -0.989859
h NaN NaN -1.413542
Описательные статистики и вычислительные методы, обсуждаемые в Обзоре структуры данных (и перечисленные здесь и здесь) все написаны с учетом отсутствующих данных. Например:
При суммировании данных значения NA (отсутствующие) будут обрабатываться как ноль.
Если все данные равны NA, результат будет равен 0.
Методы кумулятивного суммирования, такие как
cumsum()иcumprod(), по умолчанию игнорируют значения NA, но сохраняют их в результирующих массивах. Чтобы переопределить это поведение и включить значения NA, используйтеskipna=False.
In [31]: df
Out[31]:
one two three
a NaN -0.282863 -1.509059
c NaN 1.212112 -0.173215
e 0.119209 -1.044236 -0.861849
f -2.104569 -0.494929 1.071804
h NaN -0.706771 -1.039575
In [32]: df["one"].sum()
Out[32]: -1.9853605075978744
In [33]: df.mean(1)
Out[33]:
a -0.895961
c 0.519449
e -0.595625
f -0.509232
h -0.873173
dtype: float64
In [34]: df.cumsum()
Out[34]:
one two three
a NaN -0.282863 -1.509059
c NaN 0.929249 -1.682273
e 0.119209 -0.114987 -2.544122
f -1.985361 -0.609917 -1.472318
h NaN -1.316688 -2.511893
In [35]: df.cumsum(skipna=False)
Out[35]:
one two three
a NaN -0.282863 -1.509059
c NaN 0.929249 -1.682273
e NaN -0.114987 -2.544122
f NaN -0.609917 -1.472318
h NaN -1.316688 -2.511893
Сумма/произведение пустых/NaN
Предупреждение
Это поведение теперь является стандартным с версии v0.22.0 и согласуется с поведением по умолчанию в numpy; ранее сумма/произведение всех NA или пустых Series/DataFrame возвращала NaN. Дополнительная информация в v0.22.0 whatsnew.
Сумма пустой или содержащей только NA Series или столбца DataFrame равна 0.
In [36]: pd.Series([np.nan]).sum()
Out[36]: 0.0
In [37]: pd.Series([], dtype="float64").sum()
Out[37]: 0.0
Произведение пустой или содержащей только NA Series или столбца DataFrame равно 1.
In [38]: pd.Series([np.nan]).prod()
Out[38]: 1.0
In [39]: pd.Series([], dtype="float64").prod()
Out[39]: 1.0
Значения NA в GroupBy
Группы NA в GroupBy автоматически исключаются. Это поведение согласуется с R, например:
In [40]: df
Out[40]:
one two three
a NaN -0.282863 -1.509059
c NaN 1.212112 -0.173215
e 0.119209 -1.044236 -0.861849
f -2.104569 -0.494929 1.071804
h NaN -0.706771 -1.039575
In [41]: df.groupby("one").mean()
Out[41]:
two three
one
-2.104569 -0.494929 1.071804
0.119209 -1.044236 -0.861849
Дополнительную информацию см. в разделе groupby здесь.
Очистка/заполнение отсутствующих данных
Объекты pandas оснащены различными методами обработки данных для работы с отсутствующими данными.
Заполнение отсутствующих значений: fillna
fillna() может «заполнить» значения NA данными, отличными от NA, несколькими способами, которые мы иллюстрируем:
Замена NA скалярным значением
In [42]: df2
Out[42]:
one two three four five timestamp
a NaN -0.282863 -1.509059 bar True NaT
c NaN 1.212112 -0.173215 bar False NaT
e 0.119209 -1.044236 -0.861849 bar True 2012-01-01
f -2.104569 -0.494929 1.071804 bar False 2012-01-01
h NaN -0.706771 -1.039575 bar True NaT
In [43]: df2.fillna(0)
Out[43]:
one two three four five timestamp
a 0.000000 -0.282863 -1.509059 bar True 0
c 0.000000 1.212112 -0.173215 bar False 0
e 0.119209 -1.044236 -0.861849 bar True 2012-01-01 00:00:00
f -2.104569 -0.494929 1.071804 bar False 2012-01-01 00:00:00
h 0.000000 -0.706771 -1.039575 bar True 0
In [44]: df2["one"].fillna("missing")
Out[44]:
a missing
c missing
e 0.119209
f -2.104569
h missing
Name: one, dtype: object
Заполнение пробелов вперед или назад
Используя те же аргументы заполнения, что и переиндексация, мы можем распространять значения, отличные от NA, вперед или назад:
In [45]: df
Out[45]:
one two three
a NaN -0.282863 -1.509059
c NaN 1.212112 -0.173215
e 0.119209 -1.044236 -0.861849
f -2.104569 -0.494929 1.071804
h NaN -0.706771 -1.039575
In [46]: df.fillna(method="pad")
Out[46]:
one two three
a NaN -0.282863 -1.509059
c NaN 1.212112 -0.173215
e 0.119209 -1.044236 -0.861849
f -2.104569 -0.494929 1.071804
h -2.104569 -0.706771 -1.039575
Ограничение объема заполнения
Если нам нужно заполнить только последовательные пробелы до определенного числа точек данных, мы можем использовать ключевое слово limit:
In [47]: df
Out[47]:
one two three
a NaN -0.282863 -1.509059
c NaN 1.212112 -0.173215
e NaN NaN NaN
f NaN NaN NaN
h NaN -0.706771 -1.039575
In [48]: df.fillna(method="pad", limit=1)
Out[48]:
one two three
a NaN -0.282863 -1.509059
c NaN 1.212112 -0.173215
e NaN 1.212112 -0.173215
f NaN NaN NaN
h NaN -0.706771 -1.039575
Напоминание: вот доступные методы заполнения:
Метод | Действие |
|---|---|
pad / ffill | Заполнение значений вперед |
bfill / backfill | Заполнение значений назад |
При работе с временными рядами использование pad/ffill чрезвычайно распространено, чтобы «последнее известное значение» было доступно в каждый момент времени.
ffill() эквивалентно fillna(method='ffill') и bfill() эквивалентно fillna(method='bfill')
Заполнение с помощью PandasObject
Вы также можете использовать fillna с помощью словаря или Series, которые подлежат выравниванию. Метки словаря или индекс Series должны соответствовать столбцам фрейма, который вы хотите заполнить. Сценарий использования этого состоит в том, чтобы заполнить DataFrame средним значением столбца.
In [49]: dff = pd.DataFrame(np.random.randn(10, 3), columns=list("ABC"))
In [50]: dff.iloc[3:5, 0] = np.nan
In [51]: dff.iloc[4:6, 1] = np.nan
In [52]: dff.iloc[5:8, 2] = np.nan
In [53]: dff
Out[53]:
A B C
0 0.271860 -0.424972 0.567020
1 0.276232 -1.087401 -0.673690
2 0.113648 -1.478427 0.524988
3 NaN 0.577046 -1.715002
4 NaN NaN -1.157892
5 -1.344312 NaN NaN
6 -0.109050 1.643563 NaN
7 0.357021 -0.674600 NaN
8 -0.968914 -1.294524 0.413738
9 0.276662 -0.472035 -0.013960
In [54]: dff.fillna(dff.mean())
Out[54]:
A B C
0 0.271860 -0.424972 0.567020
1 0.276232 -1.087401 -0.673690
2 0.113648 -1.478427 0.524988
3 -0.140857 0.577046 -1.715002
4 -0.140857 -0.401419 -1.157892
5 -1.344312 -0.401419 -0.293543
6 -0.109050 1.643563 -0.293543
7 0.357021 -0.674600 -0.293543
8 -0.968914 -1.294524 0.413738
9 0.276662 -0.472035 -0.013960
In [55]: dff.fillna(dff.mean()["B":"C"])
Out[55]:
A B C
0 0.271860 -0.424972 0.567020
1 0.276232 -1.087401 -0.673690
2 0.113648 -1.478427 0.524988
3 NaN 0.577046 -1.715002
4 NaN -0.401419 -1.157892
5 -1.344312 -0.401419 -0.293543
6 -0.109050 1.643563 -0.293543
7 0.357021 -0.674600 -0.293543
8 -0.968914 -1.294524 0.413738
9 0.276662 -0.472035 -0.013960
Тот же результат, что и выше, но в данном случае выравнивается значение «заполнения», которое является Series.
In [56]: dff.where(pd.notna(dff), dff.mean(), axis="columns")
Out[56]:
A B C
0 0.271860 -0.424972 0.567020
1 0.276232 -1.087401 -0.673690
2 0.113648 -1.478427 0.524988
3 -0.140857 0.577046 -1.715002
4 -0.140857 -0.401419 -1.157892
5 -1.344312 -0.401419 -0.293543
6 -0.109050 1.643563 -0.293543
7 0.357021 -0.674600 -0.293543
8 -0.968914 -1.294524 0.413738
9 0.276662 -0.472035 -0.013960
Удаление меток осей с отсутствующими данными: dropna
Вы можете просто исключить метки из набора данных, которые относятся к отсутствующим данным. Для этого используйте dropna():
In [57]: df
Out[57]:
one two three
a NaN -0.282863 -1.509059
c NaN 1.212112 -0.173215
e NaN 0.000000 0.000000
f NaN 0.000000 0.000000
h NaN -0.706771 -1.039575
In [58]: df.dropna(axis=0)
Out[58]:
Empty DataFrame
Columns: [one, two, three]
Index: []
In [59]: df.dropna(axis=1)
Out[59]:
two three
a -0.282863 -1.509059
c 1.212112 -0.173215
e 0.000000 0.000000
f 0.000000 0.000000
h -0.706771 -1.039575
In [60]: df["one"].dropna()
Out[60]: Series([], Name: one, dtype: float64)
Эквивалентный dropna() доступен для Series. DataFrame.dropna имеет значительно больше вариантов, чем Series.dropna, которые можно изучить в API.
Интерполяция
В объектах Series и DataFrame есть метод interpolate(), который по умолчанию выполняет линейную интерполяцию пропущенных данных.
In [61]: ts
Out[61]:
2000-01-31 0.469112
2000-02-29 NaN
2000-03-31 NaN
2000-04-28 NaN
2000-05-31 NaN
...
2007-12-31 -6.950267
2008-01-31 -7.904475
2008-02-29 -6.441779
2008-03-31 -8.184940
2008-04-30 -9.011531
Freq: BM, Length: 100, dtype: float64
In [62]: ts.count()
Out[62]: 66
In [63]: ts.plot()
Out[63]: <AxesSubplot: >
In [64]: ts.interpolate()
Out[64]:
2000-01-31 0.469112
2000-02-29 0.434469
2000-03-31 0.399826
2000-04-28 0.365184
2000-05-31 0.330541
...
2007-12-31 -6.950267
2008-01-31 -7.904475
2008-02-29 -6.441779
2008-03-31 -8.184940
2008-04-30 -9.011531
Freq: BM, Length: 100, dtype: float64
In [65]: ts.interpolate().count()
Out[65]: 100
In [66]: ts.interpolate().plot()
Out[66]: <AxesSubplot: >
Интерполяция, учитывающая индекс, доступна через ключевое слово method:
In [67]: ts2
Out[67]:
2000-01-31 0.469112
2000-02-29 NaN
2002-07-31 -5.785037
2005-01-31 NaN
2008-04-30 -9.011531
dtype: float64
In [68]: ts2.interpolate()
Out[68]:
2000-01-31 0.469112
2000-02-29 -2.657962
2002-07-31 -5.785037
2005-01-31 -7.398284
2008-04-30 -9.011531
dtype: float64
In [69]: ts2.interpolate(method="time")
Out[69]:
2000-01-31 0.469112
2000-02-29 0.270241
2002-07-31 -5.785037
2005-01-31 -7.190866
2008-04-30 -9.011531
dtype: float64
Для числового индекса используйте method='values':
In [70]: ser
Out[70]:
0.0 0.0
1.0 NaN
10.0 10.0
dtype: float64
In [71]: ser.interpolate()
Out[71]:
0.0 0.0
1.0 5.0
10.0 10.0
dtype: float64
In [72]: ser.interpolate(method="values")
Out[72]:
0.0 0.0
1.0 1.0
10.0 10.0
dtype: float64
Также можно интерполировать с помощью DataFrame:
In [73]: df = pd.DataFrame(
....: {
....: "A": [1, 2.1, np.nan, 4.7, 5.6, 6.8],
....: "B": [0.25, np.nan, np.nan, 4, 12.2, 14.4],
....: }
....: )
....:
In [74]: df
Out[74]:
A B
0 1.0 0.25
1 2.1 NaN
2 NaN NaN
3 4.7 4.00
4 5.6 12.20
5 6.8 14.40
In [75]: df.interpolate()
Out[75]:
A B
0 1.0 0.25
1 2.1 1.50
2 3.4 2.75
3 4.7 4.00
4 5.6 12.20
5 6.8 14.40
Аргумент method предоставляет доступ к более сложным методам интерполяции. Если у вас установлен пакет scipy, вы можете передать имя 1-мерной интерполяционной функции в method. Для получения подробной информации проконсультируйтесь с полной документацией по интерполяции scipy по ссылке и справочным руководством по ссылке. Подходящий метод интерполяции зависит от типа данных, с которыми вы работаете.
Если вы имеете дело с временным рядом, который растет с возрастающей скоростью,
method='quadratic'может быть подходящим вариантом.Если у вас есть значения, приближающиеся к функции кумулятивного распределения, то
method='pchip'должно работать хорошо.Чтобы заполнить пропущенные значения с целью плавного построения графиков, рассмотрите
method='akima'.
Предупреждение
Эти методы требуют scipy.
In [76]: df.interpolate(method="barycentric")
Out[76]:
A B
0 1.00 0.250
1 2.10 -7.660
2 3.53 -4.515
3 4.70 4.000
4 5.60 12.200
5 6.80 14.400
In [77]: df.interpolate(method="pchip")
Out[77]:
A B
0 1.00000 0.250000
1 2.10000 0.672808
2 3.43454 1.928950
3 4.70000 4.000000
4 5.60000 12.200000
5 6.80000 14.400000
In [78]: df.interpolate(method="akima")
Out[78]:
A B
0 1.000000 0.250000
1 2.100000 -0.873316
2 3.406667 0.320034
3 4.700000 4.000000
4 5.600000 12.200000
5 6.800000 14.400000
При интерполяции с помощью полиномиального или сплайнового приближения необходимо также указать степень или порядок приближения:
In [79]: df.interpolate(method="spline", order=2)
Out[79]:
A B
0 1.000000 0.250000
1 2.100000 -0.428598
2 3.404545 1.206900
3 4.700000 4.000000
4 5.600000 12.200000
5 6.800000 14.400000
In [80]: df.interpolate(method="polynomial", order=2)
Out[80]:
A B
0 1.000000 0.250000
1 2.100000 -2.703846
2 3.451351 -1.453846
3 4.700000 4.000000
4 5.600000 12.200000
5 6.800000 14.400000
Сравните несколько методов:
In [81]: np.random.seed(2)
In [82]: ser = pd.Series(np.arange(1, 10.1, 0.25) ** 2 + np.random.randn(37))
In [83]: missing = np.array([4, 13, 14, 15, 16, 17, 18, 20, 29])
In [84]: ser[missing] = np.nan
In [85]: methods = ["linear", "quadratic", "cubic"]
In [86]: df = pd.DataFrame({m: ser.interpolate(method=m) for m in methods})
In [87]: df.plot()
Out[87]: <AxesSubplot: >
Другой сценарий использования — интерполяция в новых значениях. Предположим, у вас есть 100 наблюдений из некоторого распределения. И предположим, что вас особенно интересует, что происходит примерно посередине. Вы можете комбинировать методы pandas reindex и interpolate для интерполяции в новых значениях.
In [88]: ser = pd.Series(np.sort(np.random.uniform(size=100)))
# interpolate at new_index
In [89]: new_index = ser.index.union(pd.Index([49.25, 49.5, 49.75, 50.25, 50.5, 50.75]))
In [90]: interp_s = ser.reindex(new_index).interpolate(method="pchip")
In [91]: interp_s[49:51]
Out[91]:
49.00 0.471410
49.25 0.476841
49.50 0.481780
49.75 0.485998
50.00 0.489266
50.25 0.491814
50.50 0.493995
50.75 0.495763
51.00 0.497074
dtype: float64
Пределы интерполяции
Как и другие методы заполнения pandas, interpolate() принимает ключевой аргумент limit. Используйте этот аргумент, чтобы ограничить количество последовательных NaN значений, заполненных с момента последнего действительного наблюдения:
In [92]: ser = pd.Series([np.nan, np.nan, 5, np.nan, np.nan, np.nan, 13, np.nan, np.nan])
In [93]: ser
Out[93]:
0 NaN
1 NaN
2 5.0
3 NaN
4 NaN
5 NaN
6 13.0
7 NaN
8 NaN
dtype: float64
# fill all consecutive values in a forward direction
In [94]: ser.interpolate()
Out[94]:
0 NaN
1 NaN
2 5.0
3 7.0
4 9.0
5 11.0
6 13.0
7 13.0
8 13.0
dtype: float64
# fill one consecutive value in a forward direction
In [95]: ser.interpolate(limit=1)
Out[95]:
0 NaN
1 NaN
2 5.0
3 7.0
4 NaN
5 NaN
6 13.0
7 13.0
8 NaN
dtype: float64
По умолчанию NaN значения заполняются в forward направлении. Используйте параметр limit_direction для заполнения backward или both направлений.
# fill one consecutive value backwards
In [96]: ser.interpolate(limit=1, limit_direction="backward")
Out[96]:
0 NaN
1 5.0
2 5.0
3 NaN
4 NaN
5 11.0
6 13.0
7 NaN
8 NaN
dtype: float64
# fill one consecutive value in both directions
In [97]: ser.interpolate(limit=1, limit_direction="both")
Out[97]:
0 NaN
1 5.0
2 5.0
3 7.0
4 NaN
5 11.0
6 13.0
7 13.0
8 NaN
dtype: float64
# fill all consecutive values in both directions
In [98]: ser.interpolate(limit_direction="both")
Out[98]:
0 5.0
1 5.0
2 5.0
3 7.0
4 9.0
5 11.0
6 13.0
7 13.0
8 13.0
dtype: float64
По умолчанию NaN значения заполняются, независимо от того, находятся ли они внутри (окружены) существующими действительными значениями или вне существующих действительных значений. Параметр limit_area ограничивает заполнение значениями либо внутри, либо вне значений.
# fill one consecutive inside value in both directions
In [99]: ser.interpolate(limit_direction="both", limit_area="inside", limit=1)
Out[99]:
0 NaN
1 NaN
2 5.0
3 7.0
4 NaN
5 11.0
6 13.0
7 NaN
8 NaN
dtype: float64
# fill all consecutive outside values backward
In [100]: ser.interpolate(limit_direction="backward", limit_area="outside")
Out[100]:
0 5.0
1 5.0
2 5.0
3 NaN
4 NaN
5 NaN
6 13.0
7 NaN
8 NaN
dtype: float64
# fill all consecutive outside values in both directions
In [101]: ser.interpolate(limit_direction="both", limit_area="outside")
Out[101]:
0 5.0
1 5.0
2 5.0
3 NaN
4 NaN
5 NaN
6 13.0
7 13.0
8 13.0
dtype: float64
Замена общих значений
Часто нам нужно заменить произвольные значения другими значениями.
replace() в Series и replace() в DataFrame предлагают эффективный и гибкий способ выполнить такие замены.
Для Series можно заменить одно значение или список значений другим значением:
In [102]: ser = pd.Series([0.0, 1.0, 2.0, 3.0, 4.0])
In [103]: ser.replace(0, 5)
Out[103]:
0 5.0
1 1.0
2 2.0
3 3.0
4 4.0
dtype: float64
Можно заменить список значений на список других значений:
In [104]: ser.replace([0, 1, 2, 3, 4], [4, 3, 2, 1, 0])
Out[104]:
0 4.0
1 3.0
2 2.0
3 1.0
4 0.0
dtype: float64
Также можно указать словарь соответствий:
In [105]: ser.replace({0: 10, 1: 100})
Out[105]:
0 10.0
1 100.0
2 2.0
3 3.0
4 4.0
dtype: float64
Для DataFrame можно указать отдельные значения по столбцу:
In [106]: df = pd.DataFrame({"a": [0, 1, 2, 3, 4], "b": [5, 6, 7, 8, 9]})
In [107]: df.replace({"a": 0, "b": 5}, 100)
Out[107]:
a b
0 100 100
1 1 6
2 2 7
3 3 8
4 4 9
Вместо замены на заданные значения можно обработать все указанные значения как пропущенные и выполнить интерполяцию:
In [108]: ser.replace([1, 2, 3], method="pad")
Out[108]:
0 0.0
1 0.0
2 0.0
3 0.0
4 4.0
dtype: float64
Замена строк/регулярных выражений
Примечание
Строки в Python, начинающиеся с символа r, такие как r'hello world', называются «сырыми» строками. Они имеют иную семантику относительно обратных слэшей, чем строки без этого префикса. Обратные слэши в сырых строках будут интерпретироваться как экранированный обратный слэш, например, r'\' == '\\'. Вы должны прочитать об этом, если это неясно.
Замените ‘.’ на NaN (str -> str):
In [109]: d = {"a": list(range(4)), "b": list("ab.."), "c": ["a", "b", np.nan, "d"]}
In [110]: df = pd.DataFrame(d)
In [111]: df.replace(".", np.nan)
Out[111]:
a b c
0 0 a a
1 1 b b
2 2 NaN NaN
3 3 NaN d
Теперь выполните это с помощью регулярного выражения, которое удаляет окружающие пробелы (regex -> regex):
In [112]: df.replace(r"\s*\.\s*", np.nan, regex=True)
Out[112]:
a b c
0 0 a a
1 1 b b
2 2 NaN NaN
3 3 NaN d
Замените несколько разных значений (список -> список):
In [113]: df.replace(["a", "."], ["b", np.nan])
Out[113]:
a b c
0 0 b b
1 1 b b
2 2 NaN NaN
3 3 NaN d
список regex -> список regex:
In [114]: df.replace([r"\.", r"(a)"], ["dot", r"\1stuff"], regex=True)
Out[114]:
a b c
0 0 astuff astuff
1 1 b b
2 2 dot NaN
3 3 dot d
Искать только в столбце 'b' (dict -> dict):
In [115]: df.replace({"b": "."}, {"b": np.nan})
Out[115]:
a b c
0 0 a a
1 1 b b
2 2 NaN NaN
3 3 NaN d
То же, что и в предыдущем примере, но используйте регулярное выражение для поиска вместо этого (словарь regex -> словарь):
In [116]: df.replace({"b": r"\s*\.\s*"}, {"b": np.nan}, regex=True)
Out[116]:
a b c
0 0 a a
1 1 b b
2 2 NaN NaN
3 3 NaN d
Вы можете передавать вложенные словари регулярных выражений, которые используют regex=True:
In [117]: df.replace({"b": {"b": r""}}, regex=True)
Out[117]:
a b c
0 0 a a
1 1 b
2 2 . NaN
3 3 . d
В качестве альтернативы, вы можете передать вложенный словарь таким образом:
In [118]: df.replace(regex={"b": {r"\s*\.\s*": np.nan}})
Out[118]:
a b c
0 0 a a
1 1 b b
2 2 NaN NaN
3 3 NaN d
Также можно использовать группу совпадения регулярного выражения при замене (словарь regex -> словарь regex), это работает и для списков.
In [119]: df.replace({"b": r"\s*(\.)\s*"}, {"b": r"\1ty"}, regex=True)
Out[119]:
a b c
0 0 a a
1 1 b b
2 2 .ty NaN
3 3 .ty d
Вы можете передать список регулярных выражений, те из которых совпадают, будут заменены скаляром (список regex -> regex).
In [120]: df.replace([r"\s*\.\s*", r"a|b"], np.nan, regex=True)
Out[120]:
a b c
0 0 NaN NaN
1 1 NaN NaN
2 2 NaN NaN
3 3 NaN d
Все примеры с регулярными выражениями также могут быть переданы с аргументом to_replace в качестве аргумента regex. В этом случае аргумент value должен быть передан явно по имени, или regex должен быть вложенным словарем. В этом случае предыдущий пример будет выглядеть следующим образом:
In [121]: df.replace(regex=[r"\s*\.\s*", r"a|b"], value=np.nan)
Out[121]:
a b c
0 0 NaN NaN
1 1 NaN NaN
2 2 NaN NaN
3 3 NaN d
Это может быть удобно, если вы не хотите передавать regex=True каждый раз, когда хотите использовать регулярное выражение.
Примечание
В любом из вышеперечисленных примеров replace , где вы видите регулярное выражение, допустимо использовать и скомпилированное регулярное выражение.
Замена числовых значений
replace() аналогична fillna().
In [122]: df = pd.DataFrame(np.random.randn(10, 2))
In [123]: df[np.random.rand(df.shape[0]) > 0.5] = 1.5
In [124]: df.replace(1.5, np.nan)
Out[124]:
0 1
0 -0.844214 -1.021415
1 0.432396 -0.323580
2 0.423825 0.799180
3 1.262614 0.751965
4 NaN NaN
5 NaN NaN
6 -0.498174 -1.060799
7 0.591667 -0.183257
8 1.019855 -1.482465
9 NaN NaN
Замена нескольких значений возможна с помощью списка.
In [125]: df00 = df.iloc[0, 0]
In [126]: df.replace([1.5, df00], [np.nan, "a"])
Out[126]:
0 1
0 a -1.021415
1 0.432396 -0.323580
2 0.423825 0.799180
3 1.262614 0.751965
4 NaN NaN
5 NaN NaN
6 -0.498174 -1.060799
7 0.591667 -0.183257
8 1.019855 -1.482465
9 NaN NaN
In [127]: df[1].dtype
Out[127]: dtype('float64')
Вы также можете работать с DataFrame непосредственно:
In [128]: df.replace(1.5, np.nan, inplace=True)
Правила приведения типов при отсутствии данных и индексирование
Хотя pandas поддерживает хранение массивов целочисленного и булевого типов, эти типы не способны хранить пропущенные данные. Пока мы не сможем перейти к использованию собственного типа NA в NumPy, мы установили некоторые «правила приведения типов». При операциях переиндексирования, в результате которых появляются пропущенные данные, Series будет приведена в соответствии с правилами, представленными в таблице ниже.
тип данных | Приведение к |
|---|---|
целочисленный | вещественный |
булевый | объектный |
вещественный | без приведения |
объектный | без приведения |
Например:
In [129]: s = pd.Series(np.random.randn(5), index=[0, 2, 4, 6, 7])
In [130]: s > 0
Out[130]:
0 True
2 True
4 True
6 True
7 True
dtype: bool
In [131]: (s > 0).dtype
Out[131]: dtype('bool')
In [132]: crit = (s > 0).reindex(list(range(8)))
In [133]: crit
Out[133]:
0 True
1 NaN
2 True
3 NaN
4 True
5 NaN
6 True
7 True
dtype: object
In [134]: crit.dtype
Out[134]: dtype('O')
Обычно NumPy будет жаловаться, если вы попытаетесь использовать массив объектов (даже если он содержит булевы значения) вместо булева массива для получения или задания значений из ndarray (например, для выбора значений на основе каких-либо критериев). Если булевой вектор содержит значения NA, будет сгенерировано исключение:
In [135]: reindexed = s.reindex(list(range(8))).fillna(0)
In [136]: reindexed[crit]
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In [136], line 1
----> 1 reindexed[crit]
File ~/work/pandas/pandas/pandas/core/series.py:1003, in Series.__getitem__(self, key)
1000 if is_iterator(key):
1001 key = list(key)
-> 1003 if com.is_bool_indexer(key):
1004 key = check_bool_indexer(self.index, key)
1005 key = np.asarray(key, dtype=bool)
File ~/work/pandas/pandas/pandas/core/common.py:135, in is_bool_indexer(key)
131 na_msg = "Cannot mask with non-boolean array containing NA / NaN values"
132 if lib.infer_dtype(key_array) == "boolean" and isna(key_array).any():
133 # Don't raise on e.g. ["A", "B", np.nan], see
134 # test_loc_getitem_list_of_labels_categoricalindex_with_na
--> 135 raise ValueError(na_msg)
136 return False
137 return True
ValueError: Cannot mask with non-boolean array containing NA / NaN values
Однако, их можно заполнить с помощью fillna(), и всё будет работать нормально:
In [137]: reindexed[crit.fillna(False)]
Out[137]:
0 0.126504
2 0.696198
4 0.697416
6 0.601516
7 0.003659
dtype: float64
In [138]: reindexed[crit.fillna(True)]
Out[138]:
0 0.126504
1 0.000000
2 0.696198
3 0.000000
4 0.697416
5 0.000000
6 0.601516
7 0.003659
dtype: float64
pandas предоставляет тип данных для целочисленных значений с пропусками, но вы должны явно запросить его при создании Series или столбца. Обратите внимание, что мы используем прописную «I» в dtype="Int64".
In [139]: s = pd.Series([0, 1, np.nan, 3, 4], dtype="Int64")
In [140]: s
Out[140]:
0 0
1 1
2 <NA>
3 3
4 4
dtype: Int64
См. Тип данных для целочисленных значений с пропусками для получения дополнительной информации.
Экспериментальный NA скаляр для обозначения пропущенных значений
Предупреждение
Экспериментально: поведение pd.NA может измениться без предупреждения.
Введено в версии 1.0.0.
Начиная с pandas 1.0, доступен экспериментальный pd.NA скаляр (единичный экземпляр) для представления пропущенных скалярных значений. В настоящее время он используется в типе данных для целочисленных значений с пропусками, булевых значениях и специализированных строковых типах в качестве индикатора пропущенного значения.
Цель pd.NA — предоставить индикатор «пропущено», который можно использовать последовательно во всех типах данных (вместо np.nan, None или pd.NaT в зависимости от типа данных).
Например, при наличии пропущенных значений в Series с типом данных для целочисленных значений с пропусками будет использоваться pd.NA:
In [141]: s = pd.Series([1, 2, None], dtype="Int64")
In [142]: s
Out[142]:
0 1
1 2
2 <NA>
dtype: Int64
In [143]: s[2]
Out[143]: <NA>
In [144]: s[2] is pd.NA
Out[144]: True
В настоящее время pandas по умолчанию не использует эти типы данных (при создании DataFrame или Series, или при чтении данных), поэтому вам необходимо явно указать тип данных. Легкий способ преобразования в эти типы данных описан здесь.
Распространение в арифметических и сравнительных операциях
В общем случае, пропущенные значения распространяются в операциях, включающих pd.NA. Когда один из операндов неизвестен, результат операции также неизвестен.
Например, pd.NA распространяется в арифметических операциях, подобно np.nan:
In [145]: pd.NA + 1
Out[145]: <NA>
In [146]: "a" * pd.NA
Out[146]: <NA>
Есть несколько особых случаев, когда результат известен, даже если один из операндов — NA.
In [147]: pd.NA ** 0
Out[147]: 1
In [148]: 1 ** pd.NA
Out[148]: 1
В операциях равенства и сравнения pd.NA также распространяется. Это отличается от поведения np.nan, где сравнения с np.nan всегда возвращают False.
In [149]: pd.NA == 1
Out[149]: <NA>
In [150]: pd.NA == pd.NA
Out[150]: <NA>
In [151]: pd.NA < 2.5
Out[151]: <NA>
Чтобы проверить, равно ли значение pd.NA, можно использовать функцию isna():
In [152]: pd.isna(pd.NA)
Out[152]: True
Исключение из этого основного правила распространения — редукции (например, среднее или минимум), где pandas по умолчанию пропускает пропущенные значения. Подробнее см. выше.
Логические операции
Для логических операций pd.NA следует правилам трёхзначной логики (или логики Клини, аналогично R, SQL и Julia). Эта логика означает, что распространять пропущенные значения следует только тогда, когда это логически необходимо.
Например, для логической операции «или» (|), если один из операндов — True, мы уже знаем, что результат будет True, независимо от другого значения (независимо от того, было бы это пропущенное значение True или False). В этом случае pd.NA не распространяется:
In [153]: True | False
Out[153]: True
In [154]: True | pd.NA
Out[154]: True
In [155]: pd.NA | True
Out[155]: True
С другой стороны, если один из операндов — False, результат зависит от значения другого операнда. Следовательно, в этом случае pd.NA распространяется:
In [156]: False | True
Out[156]: True
In [157]: False | False
Out[157]: False
In [158]: False | pd.NA
Out[158]: <NA>
Поведение логической операции «и» (&) можно вывести, используя аналогичную логику (теперь pd.NA не будет распространяться, если один из операндов уже False):
In [159]: False & True
Out[159]: False
In [160]: False & False
Out[160]: False
In [161]: False & pd.NA
Out[161]: False
In [162]: True & True
Out[162]: True
In [163]: True & False
Out[163]: False
In [164]: True & pd.NA
Out[164]: <NA>
NA в контексте булевых значений
Поскольку фактическое значение NA неизвестно, неясно, как преобразовать NA в булево значение. Следующее приводит к ошибке:
In [165]: bool(pd.NA)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In [165], line 1
----> 1 bool(pd.NA)
File ~/work/pandas/pandas/pandas/_libs/missing.pyx:382, in pandas._libs.missing.NAType.__bool__()
TypeError: boolean value of NA is ambiguous
Это также означает, что pd.NA нельзя использовать в контексте, где оно оценивается как булево значение, например, в if condition: ..., где condition потенциально может быть pd.NA. В таких случаях, isna() можно использовать для проверки pd.NA или condition на pd.NA. Это позволяет избежать ошибок, например, предварительно заполнив пропущенные значения.
Аналогичная ситуация возникает при использовании объектов Series или DataFrame в операциях if , см. Использование операторов if/truth с pandas.
NumPy ufuncs
pandas.NA реализует протокол NumPy __array_ufunc__. Большинство ufuncs работают с NA, и обычно возвращают NA:
In [166]: np.log(pd.NA)
Out[166]: <NA>
In [167]: np.add(pd.NA, 1)
Out[167]: <NA>
Предупреждение
В настоящее время ufuncs, включающие ndarray и NA, вернут массив object-dtype, заполненный значениями NA.
In [168]: a = np.array([1, 2, 3])
In [169]: np.greater(a, pd.NA)
Out[169]: array([<NA>, <NA>, <NA>], dtype=object)
Возвращаемый тип здесь может быть изменён на другой тип массива в будущем.
См. Взаимодействие DataFrame с функциями NumPy для получения дополнительной информации об ufuncs.
Преобразование
Если у вас есть DataFrame или Series с традиционными типами, в которых пропущенные данные представлены с помощью np.nan, существуют удобные методы convert_dtypes() в Series и convert_dtypes() в DataFrame, которые могут преобразовать данные, чтобы использовать новые типы данных для целых чисел, строк и булевых значений, перечисленные здесь. Это особенно полезно после чтения наборов данных, когда читатели, такие как read_csv() и read_excel(), выводят типы данных по умолчанию.
В этом примере, хотя типы данных всех столбцов изменены, мы показываем результаты для первых 10 столбцов.
In [170]: bb = pd.read_csv("data/baseball.csv", index_col="id")
In [171]: bb[bb.columns[:10]].dtypes
Out[171]:
player object
year int64
stint int64
team object
lg object
g int64
ab int64
r int64
h int64
X2b int64
dtype: object
In [172]: bbn = bb.convert_dtypes()
In [173]: bbn[bbn.columns[:10]].dtypes
Out[173]:
player string
year Int64
stint Int64
team string
lg string
g Int64
ab Int64
r Int64
h Int64
X2b Int64
dtype: object
© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/1.5.0/user_guide/missing_data.html