Home » Leave Days by Priority

Leave Days by Priority

The table contains dates of only one year. Calculate the number of workdays for all Type of Leaves for every Name. In case of overlap of dates, priority will be ML > PL > CL Hence, if ML is for 1-Jan-23 to 20-Jan-23 and CL is for 17-Jan-23 then ML=15 but CL=0 as 17-Jan-23 is overlapping in ML range. Please also notice the order of columns in Output. ML – Medical Leave PL – Privilege Leave CL – Casual Leave

📌 Challenge Details and Links
ExcelBI Power Query Challenge Number: 152
Challenge Difficulty: ⭐️⭐️⭐️⭐️⭐️⭐️
📥Download Sample File
📥Link to the solutions on LinkedIn

Solving the challenge of Leave Days by Priority with Power Query

Power Query solution 1 for Leave Days by Priority, proposed by Bo Rydobon 🇹🇭:
let
  Source = Excel.CurrentWorkbook(){[Name = "Table1"]}[Content], 
  g = Table.ExpandTableColumn(
    Table.Group(
      Table.Sort(
        Table.AddColumn(Source, "L", each {Number.From([From Date]) .. Number.From([To Date])}), 
        {each [Name], each List.PositionOf({"ML", "PL", "CL"}, [Type of Leave])}
      ), 
      "Name", 
      {
        "T", 
        each Table.PromoteHeaders(
          Table.Transpose(
            Table.Group(
              Table.FromColumns(
                {
                  {"ML", "PL", "CL"} & [Type of Leave], 
                  {0, 0, 0}
                    & List.Transform(
                      List.Accumulate([L], {}, (s, l) => s & {List.Difference(l, List.Combine(s))}), 
                      each List.Count(List.Select(_, each Number.Mod(_, 7) > 1))
                    )
                }
              ), 
              "Column1", 
              {"L", each List.Sum([Column2])}
            )
          )
        )
      }
    ), 
    "T", 
    {"ML", "PL", "CL"}
  )
in
  g
Power Query solution 2 for Leave Days by Priority, proposed by Alejandro Simón 🇵🇦 🇪🇸:
let
  Source = Excel.CurrentWorkbook(){[Name = "Table1"]}[Content], 
  Group = Table.Combine(
    Table.Group(
      Source, 
      {"Name"}, 
      {
        {
          "All", 
          each 
            let
              a = Table.AddColumn(
                _, 
                "A", 
                each List.Select(
                  List.Transform({Number.From([From Date]) .. Number.From([To Date])}, Date.From), 
                  each Date.DayOfWeek(_) <> 6 and Date.DayOfWeek(_) <> 0
                )
              ), 
              b = Table.ExpandListColumn(a, "A")[[Name], [Type of Leave], [A]], 
              c = Table.Combine(
                Table.Group(
                  b, 
                  "A", 
                  {
                    "All1", 
                    each Table.FromRows(
                      {Record.ToList(Table.Sort(_, {"Type of Leave", each {"ML", "PL", "CL"}}){0})}, 
                      {"Name", "B", "C"}
                    )
                  }
                )[All1]
              )
            in
              c
        }
      }
    )[All]
  ), 
  Sol = Table.Pivot(Group, List.Distinct(Group[B]), "B", "C", List.Count)[[Name], [ML], [PL], [CL]]
in
  Sol
Power Query solution 3 for Leave Days by Priority, proposed by 🇮🇷 Navid Esmaeilzadeh اسماعیل زاده:
let
  Source = Excel.CurrentWorkbook(){[Name = "Table1"]}[Content], 
  C = Table.TransformColumnTypes(
    Source, 
    {
      {"Name", type text}, 
      {"From Date", type date}, 
      {"To Date", type date}, 
      {"Type of Leave", type text}
    }
  ), 
  A = Table.AddColumn(C, "date", each {Number.From([From Date]) .. Number.From([To Date])}), 
  E = Table.ExpandListColumn(A, "date"), 
  C2 = Table.TransformColumnTypes(E, {{"date", type date}}), 
  Tbl = Table.SelectColumns(C2, {"Name", "Type of Leave", "date"}), 
  S = Table.Sort(
    Tbl, 
    {
      {"Name", Order.Ascending}, 
      {"date", Order.Ascending}, 
      each List.PositionOf({"ML", "PL", "CL"}, [Type of Leave])
    }
  ), 
  R = Table.Distinct(S, {"Name", "date"}), 
  A2 = Table.AddColumn(R, "weekday", each Date.DayOfWeek([date])), 
  F = Table.SelectRows(A2, each ([weekday] <> 0 and [weekday] <> 6)), 
  G = Table.Group(F, {"Name", "Type of Leave"}, {{"Count", each Table.RowCount(_), Int64.Type}}), 
  P = Table.Pivot(G, List.Distinct(G[#"Type of Leave"]), "Type of Leave", "Count", List.Sum), 
  So = Table.ReorderColumns(P, {"Name", "ML", "PL", "CL"})
in
  So
Power Query solution 4 for Leave Days by Priority, proposed by Sandeep Marwal:
let
 priority = Table.FromRows({{"ML",1},{"PL",2},{"CL",3}}),
 S1 = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
 S2 = Table.TransformColumnTypes(S1,{{"Name", type text}, {"From Date", type date}, {"To Date", type date}}),
 S3 = Table.AddColumn(S2, "Custom", each List.Dates([From Date],1+Duration.Days([To Date]-[From Date]),hashtag#duration(1,0,0,0))),
 S4 = Table.ExpandListColumn(S3, "Custom"),
 S5 = Table.AddColumn(S4, "Day of Week", each Date.DayOfWeek([Custom]), Int64.Type),
 S6 = Table.SelectRows(S5, each ([Day of Week] <> 0 and [Day of Week] <> 6)),
 S7 = Table.NestedJoin(S6, {"Type of Leave"},priority, {"Column1"}, "Table", JoinKind.LeftOuter),
 S8 = Table.ExpandTableColumn(S7, "Table", {"Column2"}, {"Table.Column2"}),
 S9 = Table.Group(S8, {"Name", "Custom"}, {{"min", each List.Min([Table.Column2]), type nullable number}}),
 S10 = Table.NestedJoin(S9, {"min"}, priority, {"Column2"}, "Table", JoinKind.LeftOuter),
 S11 = Table.ExpandTableColumn(S10, "Table", {"Column1"}, {"Table.Column1"}),
 S12 = Table.RemoveColumns(S11,{"min"}),
 S13 = Table.Pivot(S12, List.Distinct(S12[Table.Column1]), "Table.Column1", "Custom", List.Count),
 S14 = Table.ReorderColumns(S13,{"Name", "ML", "PL", "CL"})
in
 S14
                    
                  
          
Power Query solution 5 for Leave Days by Priority, proposed by Glyn Willis:
let
  Source = Excel.CurrentWorkbook(){[Name = "Table1"]}[Content], 
  CT = Table.TransformColumnTypes(Source, {{"From Date", type date}, {"To Date", type date}}), 
  GR = Table.Group(
    CT, 
    {"Name"}, 
    {
      {
        "d", 
        each [
          ft = Table.AddColumn(
            _, 
            "dl", 
            (x) =>
              List.Select(
                List.Dates(
                  x[From Date], 
                  Duration.Days(x[To Date] - x[From Date]) + 1, 
                  Duration.From(1)
                ), 
                (z) => not List.Contains({5, 6}, Date.DayOfWeek(z))
              )
          ), 
          ml = List.Combine(Table.SelectRows(ft, (y) => y[Type of Leave] = "ML")[dl]), 
          pl = List.RemoveMatchingItems(
            List.Combine(Table.SelectRows(ft, (y) => y[Type of Leave] = "PL")[dl]), 
            ml
          ), 
          cl = List.RemoveMatchingItems(
            List.Combine(Table.SelectRows(ft, (y) => y[Type of Leave] = "CL")[dl]), 
            ml & pl
          ), 
          r = [ML = List.Count(ml), PL = List.Count(pl), CL = List.Count(cl)]
        ][r], 
        type record
      }
    }
  ), 
  #"Expanded d" = Table.ExpandRecordColumn(GR, "d", {"ML", "PL", "CL"}, {"ML", "PL", "CL"})
in
  #"Expanded d"
Power Query solution 6 for Leave Days by Priority, proposed by Arden Nguyen, CPA:
let
  Source = Excel.CurrentWorkbook(){[Name = "Table1"]}[Content], 
  a = [ML = 1, PL = 2, CL = 3], 
  b = List.TransformMany(
    Table.ToRecords(Source), 
    each {Number.From([From Date]) .. Number.From([To Date])}, 
    (x, y) =>
      x
        & Record.AddField(
          [
            Date     = Date.From(y), 
            Priority = Record.Field(a, x[Type of Leave]), 
            Workday  = Date.DayOfWeek(Date.From(y))
          ], 
          x[Type of Leave], 
          1
        )
  ), 
  c = Table.FromRecords(
    List.Select(b, each [Workday] <> 0 and [Workday] <> 6), 
    Table.ColumnNames(Source) & {"Date", "Priority", "Workday", "ML", "PL", "CL"}, 
    MissingField.UseNull
  ), 
  d = Table.Buffer(
    Table.Sort(
      c, 
      {{"Name", Order.Ascending}, {"Date", Order.Ascending}, {"Priority", Order.Ascending}}
    )
  ), 
  e = Table.Distinct(d, {"Name", "Date"}), 
  f = Table.Group(
    e, 
    {"Name"}, 
    {
      {"ML", each List.Sum([ML]) ?? 0}, 
      {"PL", each List.Sum([PL]) ?? 0}, 
      {"CL", each List.Sum([CL]) ?? 0}
    }
  )
in
  f

Solving the challenge of Leave Days by Priority with Excel

Excel solution 1 for Leave Days by Priority, proposed by Bo Rydobon 🇹🇭:
=LET(ml,{"ML","PL","CL"},s,SORT(SORTBY(A2:D17,XMATCH(D2:D17,ml))),nm,TAKE(s,,1),REDUCE(HSTACK("Name",ml),UNIQUE(nm),
LAMBDA(h,v,LET(d,FILTER(s,nm=v),r,REDUCE({0,0,0},SEQUENCE(ROWS(d)),LAMBDA(a,i,LET(f,TAKE(a,,1),t,INDEX(a,,2),v,TOCOL(SORT(TAKE(a,,2))),m,LAMBDA(x,MOD(MATCH(x,v)-1,2)*x),
b,INDEX(d,i,2),c,INDEX(d,i,3),w,WRAPROWS(SORT(VSTACK(m(b),m(c),FILTER(f-1,(f>b)*(fb)*(t
Excel solution 2 for Leave Days by Priority, proposed by Bo Rydobon 🇹🇭:
=LET(s,SORTBY(A2:D17,XMATCH(D2:D17,G1:I1)),r,REDUCE({"Name","D","E"},SEQUENCE(ROWS(s)),LAMBDA(a,i,LET(b,INDEX(s,i,2),d,SEQUENCE(INDEX(s,i,3)-b+1,,b),n,INDEX(s,i,1),
VSTACK(a,CHOOSE({1,2,3},n,FILTER(d,ISNA(XMATCH(d,FILTER(INDEX(a,,2),INDEX(a,,1)=n,0))),0),INDEX(s,i,4)))))),d,INDEX(r,,2),
DROP(SORTBY(PIVOTBY(TAKE(r,,1),DROP(r,,2),d,ROWS,3,0,,0,,WEEKDAY(d,2)<6),{1,4,2,3}),1))
Excel solution 3 for Leave Days by Priority, proposed by محمد حلمي:
=LET(N,A2:A17,p,{"ML","PL","CL"},
REDUCE(HSTACK(A1,p),SORT(UNIQUE(N)),LAMBDA(K,Y,
VSTACK(K,LET(B,FILTER(B2:D17,N=Y), m,MIN(B),
e,UNIQUE(WORKDAY(SEQUENCE(MAX(B)-m+1)+m-2,1)),v,DROP(REDUCE(0,p,LAMBDA(q,w,HSTACK(q,LET(i,FILTER(TAKE(B,,2),DROP(B,,2)=w,0),REDUCE(0,SEQUENCE(
ROWS(i)),LAMBDA(a,v,VSTACK(a,IFERROR(FILTER(e,(e>=INDEX(i,v,1))*(e<=INDEX(i,v,2))),0)))))))),1,1),
REDUCE(Y,SEQUENCE(3),LAMBDA(z,x,LET(
E,INDEX(v,,x),HSTACK(z,IF(x=1,COUNT(TAKE(v,,1)),
SUM(N(ISNA(XMATCH(FILTER(E,IFNA(E,)),
TOCOL(TAKE(v,,x-1),2)))))))))))))))
Excel solution 4 for Leave Days by Priority, proposed by LEONARD OCHEA 🇷🇴:
=LET(t,A2:D17,v,TAKE(t,,1),u,SORT(UNIQUE(v)),REDUCE({"Name","ML","PL","CL"},u,LAMBDA(a,b,LET(f,FILTER(t,v=b),t,TAKE(f,,-1),i,MIN(INDEX(f,,2)),j,MAX(INDEX(f,,3)),d,WORKDAY(i-1,SEQUENCE(,NETWORKDAYS(i,j))),m,(d>=INDEX(f,,2))*(d<=INDEX(f,,3)),g,GROUPBY(t,m,SUM,,0),h,SORTBY(g,SUBSTITUTE(TAKE(g,,1),"C","Z")),n,ROWS(g),k,BYROW(DROP(h,,1),SUM),x,k-VSTACK(0,SCAN(0,SEQUENCE(n-1)+1,LAMBDA(o,p,SUM((BYCOL(TAKE(DROP(h,,1),p),SUM)>1)*1)-o))),IFNA(VSTACK(a,HSTACK(b,TOROW(x))),0)))))

Solving the challenge of Leave Days by Priority with Python

Python solution 1 for Leave Days by Priority, proposed by Jan Willem Van Holst:
In Python:
import pandas as pd 
df = pd.read_csv(r"C:JWLENOVOPYTHONPQ challengesPower_Query_Challenge_152.csv", sep=',', usecols=[0,1,2,3], nrows=16, 
 dayfirst=True, parse_dates=[1,2])
names = df['Name'].unique().tolist()
df['working_days'] = [pd.bdate_range(row[0], row[1]) for row in zip(df['From Date'], df['To Date'])]
df_expl = df.explode('working_days').drop(['From Date', 'To Date'], axis=1)
df_group = df_expl.groupby(['Name', 'Type of Leave'])
answer = []
for elem in names:
 setML = set( df_group.get_group((elem, 'ML'))['working_days'] )
 setPL = set( df_group.get_group((elem, 'PL'))['working_days'] )
 try:
 setCL = set( df_group.get_group((elem, 'CL'))['working_days'] )
 except:
 setCL = set()
 set_intermediate = setCL.difference(setPL)
 setCL = set_intermediate.difference(setPL)
 setPL = setPL.difference(setML)
 answer.append([elem, 'ML', len(setML)])
 answer.append([elem, 'PL', len(setPL)])
 answer.append([elem, 'CL', len(setCL)])
print(answer)
                    
                  

Solving the challenge of Leave Days by Priority with R

R solution 1 for Leave Days by Priority, proposed by Konrad Gryczan, PhD:
library(tidyverse)
library(readxl)
input = read_excel("Power Query/PQ_Challenge_152.xlsx", range = "A1:D17") %>%
 janitor::clean_names()
test = read_excel("Power Query/PQ_Challenge_152.xlsx", range = "F1:I5") %>%
 janitor::clean_names()
result = input %>%
 mutate(seq = map2(from_date, to_date, seq, by = "day")) %>%
 unnest_longer(seq) %>%
 select(-c(from_date, to_date)) %>%
 mutate(value = 1) %>%
 pivot_wider(names_from = type_of_leave, values_from = value, values_fill = 0) %>%
 select(name, seq, ML, PL, CL) %>%
 mutate(sum = ML + PL + CL,
 concat = paste0(ML, PL, CL) %>% as.numeric(),
 main_leave = case_when(sum == 1 & ML == 1 ~ "ML",
 sum == 1 & PL == 1 ~ "PL",
 sum == 1 & CL == 1 ~ "CL",
 sum == 2 & concat >= 100 ~ "ML",
 sum == 2 & concat < 100 ~ "PL",
 sum == 3 ~ "ML",
 TRUE ~ "NA"),
 wday = wday(seq, week_start = 1)) %>%
 filter(!wday %in% c(6, 7)) %>%
 select(name, seq, main_leave) %>%
 mutate(main_leave = str_to_lower(main_leave)) %>%
 group_by(name, main_leave) %>%
 summarise(days = n() %>% as.numeric()) %>%
 ungroup() %>%
 pivot_wider(names_from = main_leave, values_from = days, values_fill = 0)
                    
                  

&&&

Leave a Reply