Spec-Zone.ru › pandas 1

Работа с отсутствующими данными

В этом разделе мы обсудим отсутствующие (также обозначаемые как 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: >
../_images/series_before_interpolate.png
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: >
../_images/series_interpolate.png

Интерполяция, учитывающая индекс, доступна через ключевое слово 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: >
../_images/compare_interpolations.png

Другой сценарий использования — интерполяция в новых значениях. Предположим, у вас есть 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
END_OF_DOCUMENT_MARKER

© 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API