Периодически возникают задачи в R, которые просты по своей сути, но не очевидны для тех, кто только начинает свой путь.

Представим, что в нашей организации каждый последний понедельник месяца происходит учет товара. В эти дни нет продаж. И мы бы хотели учесть это в наших прогнозах. Стоит вопрос: как в данных "выловить" эти понедельники, не используя function.

Посмотрим как это можно сделать

Для начала нам нужно импортировать библиотеки tidyverse и lubridate.

Tidyverse - это набор пакетов для языка программирования R, разработанных командой Hadley Wickham и его коллегами. Он содержит несколько пакетов, которые упрощают чтение, обработку, визуализацию и моделирование данных, используя единый фреймворк и стиль программирования. Ключевыми пакетами в Tidyverse являются ggplot2 для визуализации данных, dplyr для манипуляции с данными, tidyr для работы с данными в "длинном" формате, readr для чтения и записи данных в формате CSV и других форматах, а также purrr для функционального программирования. Tidyverse стремится к тому, чтобы программы на R были более понятными, лаконичными и легко читаемыми благодаря единому подходу к обработке и визуализации данных.

Lubridate - это пакет для языка программирования R, который облегчает работу с датами и временем. Он предоставляет удобный интерфейс для различных задач, таких как извлечение даты, времени и интервалов времени из строк, форматирование дат и времени, арифметические операции с датами и временем, конвертация между различными форматами дат и времени, и многое другое.

# импортируем библиотеки tidyverse и lubridate
library(tidyverse)
library(lubridate)

# создадим набор данных с 2023-01-01 по 2024-12-31
df <- seq(ymd("2023-01-01"), ymd("2024-12-31"), by = "day")
df <- as_tibble(df)

Создадим дополнительные столбцы: w_day (день недели), m_th (месяц) и y_r (год):

df_wmy <- df %>% 
  mutate(
    w_day = wday(value, week_start = 1),
    m_th = month(value),
    y_r = year(value)
  )

Аргумент week_start в функции wday() используется для указания дня недели, который будет считаться началом недели при вычислении дня недели для заданной даты. По умолчанию, week_start равен 7, что означает, что неделя начинается с воскресенья и заканчивается в субботу.

Если установить week_start = 1, то неделя будет начинаться с понедельника, а заканчиваться в воскресенье. Другими словами, если установить week_start = 1 и вызвать функцию wday() для даты, которая выпадает на понедельник, то функция вернет значение 1, а для даты, которая выпадает на воскресенье, функция вернет значение 7.

Например, вызов wday("2023-02-13", week_start = 1) вернет значение 1, так как 13 февраля 2023 года - это понедельник. А вызов wday("2023-02-19", week_start = 1) вернет значение 7, так как 19 февраля 2023 года - это воскресенье.

Функции month и yearпозволяют извлекать информацию в формате целого числа.

Теперь, имея дополнительные столбы, мы можем выбрать нужные нам понедельники:

df_monday <- df_w_m %>%
  filter(w_day == 1) %>% 
  group_by(m_th, y_r) %>% 
  filter(row_number() == n()) %>%
  ungroup()

Здесь мы применили фильтр, чтобы работать только в рамках того дня недели, который нас интересует. Затем группируем данные по месяцу и году, чтобы, применив выражение filter(row_number() == n()), найти последнее значение в сгруппированном фрейме данных.

Конструкция row_number() == n() является логическим выражением, используемым для фильтрации данных в R с помощью библиотеки dplyr.

Функция row_number() создает новый столбец с номерами строк в наборе данных, а n() является функцией-аргументом внутри filter(), которая возвращает номер строки, на которой находится текущая итерация фильтрации.

Таким образом, row_number() == n() сравнивает номера строк с текущим номером итерации, возвращенным n(), и возвращает TRUE, если номер строки соответствует номеру текущей итерации фильтрации.

Использование этого выражения в filter() позволяет выбрать только одну строку из набора данных, которая соответствует текущей итерации фильтрации.

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

df_w_m %>%
  filter(w_day == 1) %>% 
  group_by(m_th, y_r) %>% 
  filter(row_number() == 2) %>%
  ungroup()

Комментарии (2)


  1. lea
    00.00.0000 00:00
    +2

    find.last.mondays.of.months <- function (dates) {
      stopifnot(is(dates, 'POSIXlt'))
      all.mondays <- which(dates$wday == 1)
      first.mondays.among.all <- which(dates[all.mondays]$mday <= 7)
      all.mondays[setdiff(first.mondays.among.all - 1, 0)]
    }
    
    require(timeDate)
    test.dates <- as.POSIXlt(timeDate::timeSequence(from = '2023-01-01', to = '2024-12-31', by = 'day'))
    
    test.dates[find.last.mondays.of.months(test.dates)]

    Алгоритм: находим все понедельники (wday == 1); среди них находим первые понедельники месяца (mday <= 7); среди всех понедельников выбираем те, что предшествуют первым понедельникам.

    Для большего быстродействия нужно однократно сосчитать последние понедельники месяца за нужные годы (600 значений за 50 лет), после чего в любом наборе дат можно будет найти последние понедельники месяца оператором %in%.


  1. Ad_fesha
    00.00.0000 00:00
    +1

    Спасибо за статью.
    Часто сталкиваюсь с подобными задачами, но вместо написаний собственных функций прибегаю к пакету Алексея Селезнева ( https://habr.com/ru/post/469215/ )