Подсчет количества сотрудников по часам и дням

У меня есть идентификатор сотрудника, его часы прихода и ухода по дням. Я хочу рассчитать количество сотрудников по часам в день. Формула Excel тоже подойдет.

Мои образцы данных выглядят так:

Employee ID   Day      Clockin      Clockout
1             Mon        7:00          15:00
1             Fri        7:00          15:00
2             Wed        8:00          22:00
2             Mon        10:00         18:00
2             Fri        9:00          20:00
3             Mon        7:00          8:00 

Мой вывод должен выглядеть так:

Timestamp   Mon   Tue   Wed  Thu   Fri 
7:00        2      0     0    0     1
8:00        1      0     1    0     1
9:00        1      0     1    0     2
10:00       2      0     1    0     2
11:00       2      0     1    0     2

Я пытался изменить столбцы clock in и clock out, но это не сработало. Я бы предпочел решение в R. Я попробовал решение, упомянутое здесь in-r">Рассчитать количество дней в месяц между датами заезда и выезда в R, но в моем случае это не работает

Пример ввода

ID  Day Clockin Clockout
Employee 1  Mon 7:00    15:00
Employee 2  Mon 8:00    15:00
Employee 3  Mon 12:00   14:00
Employee 4  Mon 13:00   20:00
Employee 5  Mon 15:00   22:00
Employee 6  Mon 23:00   23:30

Пример вывода

Mon Count 
7:00    1
8:00    2
9:00    2
10:00   2
11:00   2
12:00   3
13:00   4
14:00   3
15:00   2
16:00   2
17:00   2
18:00   2
19:00   2
20:00   1
21:00   1
22:00   0
23:00   1

Если вы обратите внимание на счет, вы обнаружите, что он меняется, когда кто-то входит или уходит в течение дня.


person bp41    schedule 30.06.2020    source источник
comment
Работает!! Спасибо, что были терпеливы со мной и помогали мне.   -  person bp41    schedule 30.06.2020
comment
Я отредактировал вопрос еще раз. Если вы прокрутите вниз, вы увидите ОБНОВЛЕНИЕ 2, где я привел пример ситуации ввода и вывода. Я надеюсь, что это будет полезно.   -  person bp41    schedule 01.07.2020
comment
15:00 количество для меня равно 4   -  person akrun    schedule 01.07.2020
comment
Я обновил на основе новых данных. Некоторые из подсчетов не совпадают с ожидаемыми.   -  person akrun    schedule 01.07.2020
comment
в 14:00 сотрудник номер 3 уволился, поэтому количество станет равным 3, а в 15:00 сотрудники 2 и сотрудник 1 уволились, поэтому количество сотрудников будет равно 1, а в 15:00 сотрудник 5 пришел на работу, поэтому количество будет равно 2.   -  person bp41    schedule 01.07.2020


Ответы (3)


Это немного сложнее, чем кажется, так как если сотрудник работает в ночную смену до следующего дня, нам нужно изменить день, в который мы считаем его присутствие после полуночи.

Если уж на то пошло, я написал свою собственную функцию, которая принимает день, часы входа и выхода и возвращает последовательность времени (интервалы в 1 час) вместе с днем, связанным с каждым интервалом. Смотри ниже;

time.seq <- function(day.i, start.i, end.i, step.i = '1 hour'){
  
  require(lubridate)
  require(DescTools) ## to get the abbreviated weekdays

  start.i <- as.POSIXct(start.i, format="%H:%M")
  end.i <- as.POSIXct(end.i, format="%H:%M")
  
  if (start.i > end.i) { ## accounting for working on the next day (after midnight)
   end.i <- end.i + days(1) 
  }

  out.h <- seq(start.i, end.i , step.i)
  
  ## Going to the next day based on difference in time (unit = days)
  day.abb.ex <- c(day.abb, "Mon")
  
  out.d <- day.abb.ex[which(toupper(day.abb) == day.i) + 
            c(0, cumsum(as.numeric(diff(floor_date(out.h, unit = "day"))/86400)))]
  
  out <- list(DAY = out.d, HOUR = out.h)
  
  return(out)
}

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

library(dplyr)
library(tidyr)

df %>% 
  rowwise() %>% 
  mutate(HOUR = list(time.seq(Day, Clockin, Clockout)[["HOUR"]]),
         DAY = list(time.seq(Day, Clockin, Clockout)[["DAY"]])) %>% 
  unnest(c(HOUR, DAY)) %>% 
  count(Day=DAY, Hour = format(HOUR, '%H:%M'), name = "Count") %>%
  pivot_wider(names_from = Day, values_from = Count) 

#> # A tibble: 22 x 8
#>    Hour    Fri   Mon   Sat   Sun   Thu   Tue   Wed
#>    <chr> <int> <int> <int> <int> <int> <int> <int>
#>  1 07:00     2    NA     1    NA     1     1     1
#>  2 08:00     2    NA     1    NA     1     1     1
#>  3 09:00     2    NA     1    NA     1     1     1
#>  4 10:00     4    NA     1    NA     3     3     3
#>  5 11:00     4    NA     1    NA     3     3     3
#>  6 12:00     5     1     1    NA     4     4     4
#>  7 13:00     5     1     1    NA     4     4     4
#>  8 14:00     5     1     1    NA     4     4     4
#>  9 15:00     5     1     1    NA     4     4     4
#> 10 16:00     3     1    NA    NA     3     3     3
#> # ... with 12 more rows

Данные:

df <- structure(list(Employee.ID = c(462L, 462L, 559L, 559L, 559L, 
559L, 560L, 560L, 560L, 560L, 560L, 715L, 715L, 715L, 715L, 715L, 
791L, 791L, 791L, 791L, 802L, 802L, 802L, 802L), Day = structure(c(2L, 
4L, 7L, 8L, 6L, 2L, 3L, 7L, 8L, 6L, 2L, 3L, 7L, 2L, 4L, 5L, 7L, 
8L, 6L, 2L, 7L, 8L, 6L, 2L), .Label = c("", "FRI", "MON", "SAT", 
"SUN", "THU", "TUE", "WED"), class = "factor"), Clockin = structure(c(5L, 
5L, 2L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L, 4L, 5L, 
5L, 5L, 5L, 2L, 2L, 2L, 2L), .Label = c("", "10:00", "12:00", 
"20:00", "7:00"), class = "factor"), Clockout = structure(c(2L, 
2L, 4L, 4L, 5L, 4L, 7L, 8L, 7L, 7L, 6L, 10L, 9L, 11L, 9L, 9L, 
2L, 2L, 2L, 2L, 4L, 7L, 3L, 4L), .Label = c("", "15:00", "17:30", 
"18:00", "18:15", "19:45", "20:00", "22:00", "4:00", "4:15", 
"4:45"), class = "factor")), row.names = c(NA, 24L), class = "data.frame")
person M--    schedule 07.07.2020
comment
вам понадобится pivot_wider(names_from = Day, values_from = Count, values_fill = list(Count = 0)), все должно работать нормально - person akrun; 07.07.2020
comment
Оно работает. благодарю вас. Мне пришлось установить DescTools, чтобы заставить его работать. Я также создал функцию для решения проблемы, однако мое решение намного длиннее вашего. (Я ответил на свой вопрос, если это кому-то поможет. - person bp41; 09.07.2020

Если мы используем час с «Clockin», в R мы можем получить count из «Day», «Clockin» и преобразовать в «широкий» формат с pivot_wider

library(dplyr)
library(tidyr)
library(lubridate)
df1 %>% 
   transmute(Day = factor(Day, levels = c("MON", "TUE", "WED", "THU", "FRI", "SAT")),
         Clockin = ymd_hms(Clockin)) %>% 
   count(Day, Clockin) %>% 
   complete(Day, Clockin = seq(min(Clockin), max(Clockin), by = "1 hour"),
          fill = list(n = 0)) %>%
   mutate(Clockin = format(Clockin, "%H:%M")) %>% 
   pivot_wider(names_from = Day, values_from = n)
# A tibble: 4 x 7
#  Clockin   MON   TUE   WED   THU   FRI   SAT
#  <chr>   <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#1 07:00       0     0     0     0     1     1
#2 08:00       0     0     0     0     0     0
#3 09:00       0     0     0     0     0     0
#4 10:00       0     1     1     1     1     0

Обновлять

С новыми данными

 df2 %>%
    transmute(Day,  Hour = map2(as.POSIXct(Clockin, format = '%H:%M'),
         as.POSIXct(Clockout, format = '%H:%M'), ~ seq(.x, .y, by = '1 hour'))) %>%
    unnest(c(Hour)) %>%
    count(Day, Hour = format(Hour, '%H:%M'))
# A tibble: 17 x 3
#   Day   Hour      n
#   <chr> <chr> <int>
# 1 Mon   07:00     1
# 2 Mon   08:00     2
# 3 Mon   09:00     2
# 4 Mon   10:00     2
# 5 Mon   11:00     2
# 6 Mon   12:00     3
# 7 Mon   13:00     4
# 8 Mon   14:00     4
# 9 Mon   15:00     4
#10 Mon   16:00     2
#11 Mon   17:00     2
#12 Mon   18:00     2
#13 Mon   19:00     2
#14 Mon   20:00     2
#15 Mon   21:00     1
#16 Mon   22:00     1
#17 Mon   23:00     1

данные

df1 <- structure(list(`Employee ID` = c(462L, 462L, 559L, 559L, 559L, 
559L), Day = c("FRI", "SAT", "TUE", "WED", "THU", "FRI"), Clockin = c("1899-12-31 07:00:00", 
"1899-12-31 07:00:00", "1899-12-31 10:00:00", "1899-12-31 10:00:00", 
"1899-12-31 10:00:00", "1899-12-31 10:00:00"), Clockout = c("1899-12-31 15:00:00", 
"1899-12-31 15:00:00", "1899-12-31 18:00:00", "1899-12-31 18:00:00", 
"1899-12-31 18:15:00", "1899-12-31 18:00:00")), row.names = c(NA, 
-6L), class = "data.frame")




df2 <- structure(list(ID = c("Employee 1", "Employee 2", "Employee 3", 
"Employee 4", "Employee 5", "Employee 6"), Day = c("Mon", "Mon", 
"Mon", "Mon", "Mon", "Mon"), Clockin = c("7:00", "8:00", "12:00", 
"13:00", "15:00", "23:00"), Clockout = c("15:00", "15:00", "14:00", 
"20:00", "22:00", "23:30")), class = "data.frame", row.names = c(NA, 
-6L))
person akrun    schedule 30.06.2020
comment
Выдает ошибку: объект «День» не найден (день уже является именем столбца) - person bp41; 30.06.2020
comment
Я извиняюсь за то, что снова и снова повторяю формулировку проблемы. Результат, который я ожидаю, немного отличается от того, что я получаю, судя по приведенному выше коду. Я добавил больше деталей в свой вопрос выше (если вы прокрутите вниз). Если кто-то пришел в 7:00, а следующие часы в этот день — в 10:00, тогда общее количество должно отображать 1 для 7:00, 8:00, 9:00, а в 10:00 количество будет равно 2. Он останется 2, пока другой человек не заработает в тот же день. - person bp41; 30.06.2020
comment
вы вычитаете часы из общего количества? потому что в 20:00 счет равен 2; в 21:00 нет активности, поэтому счетчик остается равным 2, в 22:00 сотрудник 5 уволился, поэтому счет будет равен 1, в 23:00 пришел сотрудник 6, поэтому счетчик станет равным 2. - person bp41; 01.07.2020
comment
@Belle Белль, я пытался понять твой предыдущий комментарий. В моем коде он перебирает каждую строку «Clockiin», «ClockOut», получает последовательность часов от соответствующего Clockin до «ClokOut» на 1 час, раскладывает его, а затем делает подсчет - person akrun; 01.07.2020
comment
@belle Когда вы говорите employee 5 clocked out, вы удаляете из него «22:00»? - person akrun; 01.07.2020
comment
да. когда сотрудник 5 уволится, я сниму его со счета. - person bp41; 01.07.2020

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

  #loading libraries
    library(lubridate)
    library(readxl)
    library(stringr)
    library(tidyr)
    #read data
    df <- read_excel('data_sample.xlsx',
                     col_types = c('numeric', 'text', 'date', 'guess', 'guess','skip', 'numeric'))
    #convert clocks to float for faster comparisons
    df$`Clock In` = hour(df$`Clock In`) + minute(df$`Clock In`) / 60
    df$`Clock Out` = hour(df$`Clock Out`) + minute(df$`Clock Out`) / 60
    #remove shallow records
    df <- df[!(df$`Clock In` == df$`Clock Out`),]
    #24 hours strings
    hours = lapply(0 : 23, function(i) str_pad(paste(toString(i), ':00', sep=""), 5, pad = '0'))
    #empty presence matrix
    hours_mat = c()
    #fill the presence matrix with ines when exists in work place
    for (r in 1:nrow(df)) {
      lis = c()
      for (i in 0:23) {
        if (i < df[r, 'Clock In'] | i >= df[r, 'Clock Out']){
          lis[i + 1] <- 0
        }
        else {
          lis[i + 1] <- 1
        }
      }
      hours_mat <- rbind(hours_mat, lis)
    }
    #convert  matrix to dataframe
    hours_df = as.data.frame(hours_mat)
    colnames(hours_df) <- hours
    #bind the matrix to the original dataframe
    final_df <- cbind(df, hours_df)
    #aggregate presence count over date in every position
    result <- aggregate(final_df[1:nrow(final_df),7:ncol(final_df)],
                        by=list(Date = final_df$Date, Position = final_df$Position),
                        FUN=sum)
    #factorize dates for converting from wide to long format
    result$Date = factor(result$Date)
    #wide to long format
    long_result <- gather(result, Timestamp, Count, '00:00':'23:00',
                          factor_key = TRUE)
    #long to wide format using dates
    result_wide <- spread(long_result, Date, Count)
    #to select a particular position, uncomment this line :
    #result_wide <- result_wide[result_wide$Position == 'your required position as number']
    #write the final output to "output.csv"
    write.csv(result_wide, 'output.csv')

и образцы данных следующие:

structure(list(`Employee Number` = c(1, 1, 1, 1, 1, 1, 1, 1, 
1, 1, 1, 1, 1, 1, 1, 1, 1, 1), Day = c("THU", "FRI", "SAT", "SUN", 
"WED", "THU", "FRI", "SAT", "SUN", "WED", "THU", "THU", "FRI", 
"SAT", "SUN", "WED", "THU", "THU"), Date = structure(c(1577923200, 
1578009600, 1578096000, 1578182400, 1578441600, 1578528000, 1578614400, 
1578700800, 1578787200, 1579046400, 1579132800, 1579132800, 1579219200, 
1579305600, 1579392000, 1579651200, 1579737600, 1579737600), class = c("POSIXct", 
"POSIXt"), tzone = "UTC"), ClockIn = structure(c(-2209021200, 
-2209021200, -2209021200, -2209021200, -2209021200, -2209023000, 
-2209021200, -2209021200, -2209021200, -2209021200, -2209075200, 
-2209021200, -2209021200, -2209021200, -2209021200, -2209021200, 
-2209075200, -2209021200), class = c("POSIXct", "POSIXt"), tzone = "UTC"), 
    `Clock Out` = structure(c(-2208992400, -2208992400, -2208992400, 
    -2208992400, -2208992400, -2208994200, -2208992400, -2208992400, 
    -2208992400, -2208992400, -2209075200, -2208999600, -2208992400, 
    -2208992400, -2208992400, -2208992400, -2209075200, -2208999600
    ), class = c("POSIXct", "POSIXt"), tzone = "UTC"), Department = c(20, 
    20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 
    20, 20), Position = c(35, 35, 35, 35, 35, 35, 35, 35, 35, 
    35, 35, 35, 35, 35, 35, 35, 35, 35)), row.names = c(NA, -18L
), class = c("tbl_df", "tbl", "data.frame"))
person bp41    schedule 09.07.2020