Pandas nat что это
Перейти к содержимому

Pandas nat что это

  • автор:

Что нового в pandas 1.0?

Follow us on Google Plus Follow us on rss

В конце января 2020 вышло большое обновление библиотеки pandas – 1.0. Представляем вам обзор изменений и дополнений, которые по нашему мнению являются интересными и заслуживают внимания.

  • pd.NA
  • Типы pandas для работы со строками и boolean-значениями
  • Инструмент конвертирования типов
  • Конвертор в markdown
  • Еще изменения и дополнения…

Мажорный релиз 1.0 принес с собой улучшения существующих инструментов, новые дополнения (они пока имеют статус экспериментальных), ряд изменений в API, которые ломают обратную совместимость, багфиксы и изменения, увеличивающие производительность библиотеки.

pd.NA

Первое, с чего хотелось бы начать – это pd.NA – “значение”, которое pandas будет использовать для обозначения отсутствующих данных.

В предыдущих версиях pandas для обозначения отсутствующих данных использовались следующие значения: NaN, NaT и None. NaN – это отсутствующее значение в столбце с числовым типом данных, оно является аббревиатурой от Not a Number (пришло из numpy: np.NaN). NaT – это отсутствующее значение для данных типа DateTime, аббревиатура от Not a Time (является частью библиотеки pandas). None используется если тип данных object, такой тип имеют, например, элементы типа str (пришло из Python).

Рассмотрим работу с отсутствующими данными на примерах:

> d = <"A":[None, "test2", "test3"], "B": [1.01, np.nan, 3.45], "C": [date(2019, 1, 29), datetime.now(), None], "D":[1, 2, None]>> df = pd.DataFrame(d)

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

> pd.Series([4, 5, None]) 0 4.0 1 5.0 2 NaN dtype: float64

Если тип указать, то в pandas 1.0 будет использован pd.NA:

>pd.Series([4, 5, None], dtype="Int64") 0 4 1 5 2 dtype: Int64

Либо можно задать pd.NA напрямую:

> pd.Series([4, 5, pd.NA], dtype="Int64") 0 4 1 5 2 dtype: Int64

Для строковых и boolean значений это работает аналогично:

> pd.Series([None, "test2", "test3"]) 0 None 1 test2 2 test3 dtype: object > pd.Series([None, "test2", "test3"], dtype='string') 0 1 test2 2 test3 dtype: string > pd.Series([True, False, None]) 0 True 1 False 2 None dtype: object > pd.Series([True, False, None], dtype='boolean') 0 True 1 False 2 dtype: boolean

Типы pandas для работы со строками и boolean-значениями

Появился тип StringDtype для работы со строковыми данными (до этого строки хранились в object-dtype NumPy массивах). При создании структуры pandas необходимо указать тип StringDtype либо string:

> pd.Series([None, "test2", "test3"], dtype=pd.StringDtype()) 0 1 test2 2 test3 dtype: string > pd.Series([None, "test2", "test3"], dtype='string') 0 1 test2 2 test3 dtype: string

Также добавлен тип BooleanDtype для хранения boolean значений. В предыдущих версиях столбец данных типа bool не мог содержать отсутствующие значения, тип BooleanDtype поддерживает эту возможность:

> pd.Series([False, True, None], dtype=pd.BooleanDtype()) 0 False 1 True 2 dtype: boolean > pd.Series([False, True, None], dtype='boolean') 0 False 1 True 2 dtype: boolean

Инструмент конвертирования типов

Метод convert_dtypes поддерживает работу с новыми типами:

> d = <"A":[None, "test2", "test3"], "B": [1, np.nan, 3], "C": [True, False, None], "D":[1, 2, None]>> df = pd.DataFrame(d) > df_conv = df.convert_dtypes() > df

> df_conv

> df.dtypes A object B float64 C object D float64 dtype: object > df_conv.dtypes A string B Int64 C boolean D Int64 dtype: object

Конвертор в markdown

В pandas 1.0 добавлен метод to_markdown() для конвертирования структур pandas в markdown таблицы:

> d = <"A":[None, "test2", "test3"], "B": [1, np.nan, 3], "C": [True, False, None], "D":[1, 2, None]>> df = pd.DataFrame(d) > print(df.to_markdown()) | | A | B | C | D | |---:|:------|----:|:------|----:| | 0 | | 1 | True | 1 | | 1 | test2 | nan | False | 2 | | 2 | test3 | 3 | | nan | > s = pd.Series([None, "test2", "test3"], dtype='string') > print(s.to_markdown()) | | 0 | |---:|:------| | 0 | | | 1 | test2 | | 2 | test3 |

Еще изменения и дополнения…

  • Ускорение работы функций rolling.apply и expanding.apply;
  • Возможность игнорирования индекса при сортировке DataFrame:
> df = pd.DataFrame() > df

> df.sort_values("A")

> df.sort_values("A", ignore_index=True)

  • Более информативный info()
> d = <"A":[None, "test2", "test3"], "B": [1, np.nan, 3], "C": [True, False, None], "D":[1, 2, None]>> df = pd.DataFrame(d).convert_dtypes() > df.info() RangeIndex: 3 entries, 0 to 2 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 A 2 non-null string 1 B 2 non-null Int64 2 C 2 non-null boolean 3 D 2 non-null Int64 dtypes: Int64(2), boolean(1), string(1) memory usage: 212.0 bytes

Более подробно про остальные изменения и дополнения можете прочитать на официальной странице сайта pandas.

P.S.

Вводные уроки по “Линейной алгебре на Python” вы можете найти соответствующей странице нашего сайта . Все уроки по этой теме собраны в книге “Линейная алгебра на Python”.

Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. Для начала вы можете познакомиться с вводными уроками. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.

Getting Pandas NaT to propagate like NaN

I’m trying to take the min and max of a couple Pandas Series objects containing datetime64 data in the face of NaT. np.minimum and np.maximum work the way I want if the dtype is float64. That is, once any element in the comparison is NaN, NaN will be the result of that comparison. For example:

>>> s1 0 0.0 1 1.8 2 3.6 3 5.4 dtype: float64 >>> s2 0 10.0 1 17.0 2 NaN 3 14.0 dtype: float64 >>> np.maximum(s1, s2) 0 10.0 1 17.0 2 NaN 3 14.0 dtype: float64 >>> np.minimum(s1, s2) 0 0.0 1 1.8 2 NaN 3 5.4 dtype: float64 

This doesn’t work if s1 and s2 are datetime64 objects:

>>> s1 0 2199-12-31 1 2199-12-31 2 2199-12-31 3 2199-12-31 dtype: datetime64[ns] >>> s2 0 NaT 1 2018-10-30 2 NaT 3 NaT dtype: datetime64[ns] >>> np.maximum(s1, s2) 0 2199-12-31 1 2199-12-31 2 2199-12-31 3 2199-12-31 dtype: datetime64[ns] >>> np.minimum(s1, s2) 0 2199-12-31 1 2018-10-30 2 2199-12-31 3 2199-12-31 dtype: datetime64[ns] 

I expected indexes 0, 2 and 3 to turn up as NaT whether computing the min or max. (I realize numpy’s functions might not have been the best choice, but I was not successful finding suitable Pandas analogs.) After doing a bit of reading, I came to realize NaT is only approximately NaN, the latter having a proper floating point representation. Further reading suggested no simple way to have NaT «pollute» these comparisons. What’s the correct way to get NaT to propagate in min/max comparisons the way NaN does in a floating point context? Maybe there are Pandas equivalents to numpy. which are NaT-aware?

Делаем сессии из лога событий с помощью Pandas

Волею судеб передо мной встала необходимость разбить большущий лог событий на сессии. Не буду приводить полный лог, а покажу упрощенный пример:

Структура данных лога представляет собой:

  • id — порядковый номер события в логе
  • user_id — уникальный идентификатор пользователя, совершившего событие (при решении реальной задачи анализа лога в качестве user_id может выступать IP-адрес пользователя или, например, уникальный идентификатор cookie-файла)
  • date_time — время совершения события
  • page — страница, на которую перешел пользователь (для решения задачи эта колонка не несет никакой пользы, я привожу её для наглядности)

Задача состоит в том, чтобы разбить последовательность событий (просмотров страниц) на вот такие блоки, которые будут сессиями:

Говоря «разбить», я не имею в виду разделить и сохранить в виде разных массивов данных или ещё что-то подобное. Тут речь идёт о том, чтобы каждому событию сопоставить номер сессии, в которую это событие входит.

Критерий сессии в моем случае — она живет полчаса после предыдущего совершенного события. Например, в строке 6 пользователь перешел на страницу /catalog в 8:21, а следующую страницу /index (строка 7) посмотрел в 9:22. Разница между просмотром страниц составляет 1 час 1 минуту, а значит эти просмотры относятся к разным сессиям этого пользователя.

Все это дело я буду делать на Питоне при помощи Pandas в Jupyter Notebook. Вот ссылка на ноутбук.

Алгоритм

Итак, у нас есть ’event_df’ — это датафрейм, в котором содержатся данные о событиях в привязке к пользователям:

1 События сгенерированные разными пользователями идут в хронологическом порядке. Для удобства отсортируем их по user_id, тогда события каждого пользователя будут идти последовательно:

event_df = event_df.sort_values('user_id')

2 В колонке ’diff’ для каждого события отдельного пользователя посчитаем разницу между временем посещения страницы и временем посещения предыдущей страницы. Если страница была первой для пользователя, то значение в колонке ’diff’ будет NaT, т. к. нет предыдущего значения

Обратите внимание, что совместно с функцией diff я использую для группировки пользователей groupby, чтобы считать разницу между временными метками отдельных пользователей. Без использования groupby мы бы просто брали все временные метки и считали бы между ними разницу, что было бы неправильно, так как события относятся к разным пользователям.

event_df['diff'] = event_df.groupby('user_id')['date_time'].diff(1)

Кое-что уже проклевывается. Мы нашли такие события, которые будут начальными точками для сессий:

3 Из основного датафрейма ’event_df’ создадим вспомогательный датафрейм ’session_start_df’. Этот датафрейм будет содержать события, которые будут считаться первыми событиями сессий. К таким событиям относятся все события, которые произошли спустя более чем 30 минут после предыдущего, либо события, которые были первыми для пользователя (NaT в колонке ’diff’).

Также создадим во вспомогательном датафрейме колонку ’session_id’, которая будет содержать в себе id первого события сессии. Она пригодится, чтобы корректно отобразить идентификатор сессии, когда будем объединять данные из основного и вспомогательного датафреймов.

sessions_start_df = event_df[(event_df['diff'].isnull()) | (event_df['diff'] > '1800 seconds')] sessions_start_df['session_id'] = sessions_start_df['id']

Вспомогательный датафрейм ’session_start_df’ выглядит так:

4 С помощью функции merge_asof объединим между собой данные основного и вспомогательного датафреймов. Эта функция позволяет объединить данные двух датафреймов схожим образом с левым join’ом, но не по точному соответствию ключей, а по ближайшему. Примеры и подробности в документации.

Для корректной работы функции merge_asof оба датафрейма должны быть отсортированы по ключу, на основе которого будет происходить объединение. В нашем случае это колонка ’id’.

Обратите внимание, что из датафрейма ’session_start_df’ я выбираю только колонки ’id’, ’user_id’ и ’session_id’, так как остальные колонки особо не нужны.

event_df = event_df.sort_values('id') sessions_start_df = sessions_start_df.sort_values('id') event_df = pd.merge_asof(event_df,sessions_start_df[['id','user_id','session_id']],on='id',by='user_id')

В итоге получаем вот такой распрекрасный объединенный датафрейм, в котором в колонке ’session_id’ указан уникальный идентификатор сессии:

Дополнительные манипуляции

1 Найдем события, которые были первыми в сессиях. Это будет полезно, если мы захотим определить страницы входа.

Обнаружить эти события предельно просто: их идентификаторы будут равны идентификаторам сессии. Для этого создадим колонку ’is_first_event_in_session’, в которой сравним между собой значения колонок ’id’ и ’session_id’.

event_df['is_first_event_in_session'] = event_df['id'] == event_df['session_id']

2 Вычислим время, проведенное на странице, руководствуясь временем посещения следующей страницы

Для этого сначала считаем разницу между предыдущей и следующей страницей внутри сессии. Мы уже делали такое вычисление, когда считали разницу между временем посещения страниц пользователем. Только тогда мы группировали по ’user_id’, а теперь будем по ’session_id’.

event_df['time_on_page'] = event_df.groupby(['session_id'])['date_time'].diff(1)

Но diff со смещением в 1 строку считает разницу между посещением последующей страницы относительно предыдущей, поэтому время пребывания на предыдущей странице будет записано в строку следующего события:

Нам нужно сдвинуть значение столбца ’time_on_page’ на одну строку вверх внутри отдельно взятой сессии. Для этого нам пригодится функция shift.

event_df['time_on_page'] = event_df.groupby(['session_id'])['time_on_page'].shift(-1)

Получили то, что нужно:

Значения в столбце ’time_on_page’ имеют специфический тип datetime64, который не всегда удобен для арифметических операций, поэтому преобразуем ’time_on_page’ в секунды.

event_df['time_on_page'] = event_df['time_on_page'] / np.timedelta64(1, 's')

3 На основе полученных данных очень просто посчитать различные агрегаты

event_df['user_id'].nunique() # Количество пользователей event_df['session_id'].nunique() # Количество сессий event_df['id'].count() # Количество просмотров страниц (событий) event_df['time_on_page'].mean() # Среднее время просмотра страниц

Заключение

Таким образом, используя несколько не самых очевидных функций в Pandas (например, merge_asof мне довелось применять впервые), можно формировать сессии на основе лога событий. Логом событий могут выступать логи сервера, какой-нибудь клик-стрим в SaaS-сервисах, сырые данные систем веб-аналитики.

Удачи и новых аналитических достижений!

Вступайте в группу на Facebook и подписывайтесь на мой канал в Telegram, там публикуются интересные статьи про анализ данных и не только.

Как правильно проверить на Null\NaT поле фраймворка pandas и записать его в базу postgres?

Последняя колонка содержит «NaT» , в базе данных данное поле update_dt типа timestamp(0).

psycopg2.errors.InvalidDatetimeFormat: ОШИБКА: неверный синтаксис для типа timestamp: «NaT»

Если сделать так df = df.where(pd.notnull(df), ‘Null’).
psycopg2.errors.InvalidDatetimeFormat: ОШИБКА: неверный синтаксис для типа timestamp: «Null»
Как проверить на Null и записать данные timestamp не используя case (if) sql.

  • Вопрос задан 21 дек. 2022
  • 523 просмотра

2 комментария

Простой 2 комментария

mayton2019 @mayton2019

Две просьбы.
1) Придумай названия колонкам. Чтоб не мучать форум такими формами как «последняя колонка или пред-последняя»
2) Оформи это в виде таблицы — тогда ясность появляется и все можено порешать.

TosterModerator

Модератор @TosterModerator

Фрагменты кода надо размещать в виде текста и оборачивать тэгом code для корректного отображения. Удобно делать кнопкой
Это обязательно, см.п.3.8 Регламента.
Сюда же относится traceback, ввод и вывод в консоли и другая структурированная текстовая инфа.
Если таблицу обернуть тэгом, тоже будет выглядеть лучше.

Решения вопроса 1

gnifajio

Совершенствуюсь каждый день

В pandas можно использовать функцию isnull для проверки поля на наличие значения NaN (Not a Number) или NaT (Not a Time). Например, чтобы проверить поле update_dt на наличие значения NaT, можно использовать следующий код:
df[‘update_dt’].isnull()
Этот код вернет булевый сериес, где True указывает на то, что в соответствующей ячейке поля update_dt стоит значение NaT, а False — значение присутствует.

Чтобы записать эти данные в базу данных, можно использовать конструкцию INSERT INTO . SELECT . FROM с вложенным запросом. Вот пример такого запроса, который записывает в таблицу table_name все записи из df, где поле update_dt не равно NaT:

INSERT INTO table_name (column1, column2, . update_dt) SELECT column1, column2, . update_dt FROM df WHERE df['update_dt'].isnull() = False

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *