Home » Dso!

Dso!

Solving Dso challenge by Power Query, Power BI, Excel, Python and R

Using the sales transactions from the provided table and the account balance as of 31/08/2024, calculate the Daily Sales Outstanding (DSO) for each customer. To calculate the DSO for customer C, since the sum of the last three transactions matches the balance amount, the DSO is the weighted average (based on Sales) of duration between 03/04/2024 and 31/08/2024.Note: Sample calculation for Customer A is provided in the next table.

📌 Challenge Details and Links
Challenge Number: 118
Challenge Difficulty: ⭐⭐⭐
Designed by: Mahmoud Bani Asadi
📥Download Sample File
📥Link to the solutions on LinkedIn

Solving the challenge of Dso! with Power Query

Power Query solution 1 for Dso!, proposed by Zoran Milokanović:
let
  Source = each Excel.CurrentWorkbook(){[Name = _]}[Content], 
  S = Table.Sort(
    Table.Group(
      Table.Join(Source("Table1"), "Customer", Source("Table2"), "Customer"), 
      {"Customer", "Balance"}, 
      {
        "DSO", 
        each 
          let
            s = List.Select(
              List.Accumulate(
                List.Reverse(Table.ToRows(_)), 
                {}, 
                (b, n) =>
                  let
                    i = n{3} - {List.Sum(List.Zip(b){4}), 0}{Byte.From(b = {})}
                  in
                    b & {n & {if i >= n{2} then n{2} else if i > 0 then i else 0}}
              ), 
              each _{4} > 0
            )
          in
            List.Sum(
              List.Transform(s, each Number.From(#datetime(2024, 8, 31, 0, 0, 0) - _{0}) * _{4})
            )
              / List.Sum(List.Zip(s){4})
      }
    ), 
    "Customer"
  )
in
  S
Power Query solution 2 for Dso!, proposed by Alejandro Simón 🇵🇦 🇪🇸:
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
Tbl2 = Excel.CurrentWorkbook(){[Name="Table2"]}[Content],
Sol = Table.Sort(Table.Group(Source, {"Customer"}, {{"DSO Day", (x)=> 
 let
 a = Table.ReverseRows(x),
 b = {
hashtag
#date(2024,8,31)}&List.Transform(a[Date], Date.From),
 c = List.Transform({1..List.Count(b)-1}, each Number.From(b{0}-b{_})),
 d = List.Transform({1..List.Count(c)}, each List.Sum(List.FirstN(a[Sales],_))),
 e = Table.SelectRows(Tbl2, each [Customer]=x[Customer]{0})[Balance]{0},
 f = List.Select(d, each _<=e),
 g = List.FirstN(a[Sales], List.Count(f)) & {e-List.Last(f)},
 h = List.Sum(List.Transform(List.Zip({g,c}), each _{0}*_{1})/e)
 in h}}), "Customer")
in
Sol
Power Query solution 3 for Dso!, proposed by Kris Jaganah:
let
  A = (x) => Excel.CurrentWorkbook(){[Name = x]}[Content], 
  B = Table.AddColumn(
    A("Table2"), 
    "DSO (day)", 
    each 
      let
        a = Table.Sort(Table.SelectRows(A("Table1"), (y) => y[Customer] = [Customer]), {"Date", 1}), 
        b = Table.InsertRows(
          a, 
          0, 
          {[Date = #datetime(2024, 8, 31, 0, 0, 0), Customer = [Customer], Sales = - [Balance]]}
        ), 
        c = Table.AddIndexColumn(b, "Id", 1), 
        d = Table.AddColumn(c, "Sum", each List.Sum(List.FirstN(c[Sales], [Id]))), 
        e = Table.RemoveFirstN(d, 1), 
        f = Table.AddColumn(e, "Co", each Number.From([Sum] > 0)), 
        g = Table.AddColumn(
          f, 
          "Ans", 
          each 
            let
              p = List.Sum(List.FirstN(f[Co], [Id] - 1)), 
              q = if p = 1 then [Sales] - [Sum] else if p > 1 then 0 else [Sales], 
              r = Number.From(((b[Date]{0} - [Date]) * q) / - b[Sales]{0})
            in
              r
        ), 
        h = Table.Group(g, {"Customer"}, {"Ans", each Number.Round(List.Sum([Ans]), 2)})[Ans]{0}
      in
        h
  )
in
  B

Solving the challenge of Dso! with Excel

Excel solution 1 for Dso!, proposed by Bo Rydobon 🇹🇭:
=MAP(F3:F5,
    G3:G5,
    LAMBDA(a,
    b,
    LET(c,
    SORT(
        FILTER(
            B3:D25,
            C3:C25=a
        ),
        ,
        -1
    ),
    s,
    TAKE(
        c,
        ,
        -1
    ),
    t,
    s+SCAN(
        b,
        -s,
        SUM
    ),
    SUM(IF(s>t,
    (t>0)*t,
    s)*("2024/8/31"-TAKE(
        c,
        ,
        1
    )))/b)))
Excel solution 2 for Dso!, proposed by Oscar Mendez Roca Farell:
=MAP(F3:F5,
     G3:G5,
     LAMBDA(a,
     b,
     LET(f,
     SORT(
         FILTER(
             B3:D25,
              C3:C25=a
         ),
          ,
         -1
     ),
     s,
     DROP(
         f,
         ,
         2
     ),
     d,
     DROP(
         BYROW(
             HSTACK(
                 s,
                  b-VSTACK(
                      0,
                       SCAN(
                           ,
                            s,
                            SUM
                       )
                  )
             ),
              LAMBDA(
                  r,
                   MAX(
                       ,
                        MIN(
                            r
                        )
                   )
              )
         ),
          -1
     ),
     SUM(d*(45535-TAKE(
         f,
         ,
         1
     ))/b))))
Excel solution 3 for Dso!, proposed by Julian Poeltl:
=MAP(
    F3:F5,
    G3:G5,
    LAMBDA(
        A,
        B,
        LET(
            T,
            SORT(
                B3:D25,
                ,
                -1
            ),
            D,
            DATE(
                2024,
                8,
                31
            )-TAKE(
                T,
                ,
                1
            ),
            C,
            CHOOSECOLS(
                T,
                2
            ),
            S,
            TAKE(
                T,
                ,
                -1
            ),
            F,
            FILTER(
                S,
                C=A
            ),
            R,
            B-SCAN(
                0,
                F,
                SUM
            ),
            I,
            IF(
                R>0,
                F,
                0
            ),
            X,
            IFNA(
                XMATCH(
                    0,
                    I
                ),
                ROWS(
                    I
                )
            ),
            V,
            VSTACK(
                TAKE(
                    I,
                    X-1
                ),
                INDEX(
                    R,
                    X-1
                )
            ),
            SUM(
                V*TAKE(
                    FILTER(
                        D,
                        C=A
                    ),
                    X
                )
            )/B
        )
    )
)
Excel solution 4 for Dso!, proposed by Kris Jaganah:
=MAP(F3:F5,
    G3:G5,
    LAMBDA(x,
    y,
    LET(a,
    GROUPBY(
        B3:B25,
        D3:D25,
        SUM,
        ,
        0,
        -1,
        C3:C25=x
    ),
    b,
    DROP(
        a,
        ,
        1
    ),
    c,
    TAKE(
        a,
        ,
        1
    ),
    d,
    SCAN(
        -y,
        b,
        SUM
    ),
    e,
    SCAN(
        ,
        N(
            d>0
        ),
        SUM
    ),
    f,
    IFS(
        e=1,
        b-d,
        e>1,
        0,
        1,
        b
    ),
    SUM(((DATE(
        2024,
        8,
        31
    )-c)*f)/y))))
Excel solution 5 for Dso!, proposed by Mahmoud Bani Asadi:
=MAP(
    G3:G5,
    F3:F5,
    LAMBDA(
        x,
        y,        LET(
            
            a,
            SORT(
                FILTER(
                    B3:D25,
                    C3:C25=y
                ),
                1,
                -1
            ),
            
            b,
            INDEX(
                a,
                ,
                3
            ),
            
            c,
            SCAN(
                0,
                b,
                SUM
            ),
            
            d,
            DROP(
                VSTACK(
                    0,
                    c
                ),
                -1
            ),
            
            e,
            IF(
                c
Excel solution 6 for Dso!, proposed by Sunny Baggu:
=MAP(
 F3:F5, G3:G5, LAMBDA(x,
     y, LET(
 f,
     45535, _a,
     SORT(FILTER(B3:D25,
     (C3:C25 = x) * (B3:B25 < f)),
     ,
     -1), _l,
     TAKE(
         _a,
          ,
          -1
     ), _d,
     f - TAKE(
         _a,
          ,
          1
     ), _rs,
     SCAN(
         0,
          _l,
          LAMBDA(
              a,
               v,
               a + v
          )
     ), _b,
     y - _rs, _c,
     VSTACK(
         FILTER(
             _l,
              _b > 0
         ),
          TAKE(
              FILTER(
                  _b,
                   _b > 0
              ),
               -1
          )
     ), SUMPRODUCT(
     _c * TAKE(
         _d,
          ROWS(
              _c
          )
     )
 ) / y
 )
 )
)
Excel solution 7 for Dso!, proposed by Eddy Wijaya:
=LET(    I,
    SORT(
        B3:D25,
        1,
        -1
    ),    dat,
    "8/31/24",    MAP(
        UNIQUE(
            CHOOSECOLS(
                I,
                2
            )
        ),
        LAMBDA(
            m,
            LET(
                
                f_a,
                FILTER(
                    I,
                    CHOOSECOLS(
                I,
                2
            )=m
                ),
                
                limit,
                XLOOKUP(
                    m,
                    F3:F5,
                    G3:G5
                ),
                
                calc,
                SCAN(
                    0,
                    DROP(
                        f_a,
                        ,
                        2
                    ),
                    LAMBDA(
                        a,
                        v,
                        a+v
                    )
                ),
                
                res,
                IF(
                    calc0,
                    res,
                    0
                ),
                
                day_col,
                BYROW(
                    TAKE(
                        f_a,
                        ,
                        1
                    ),
                    LAMBDA(
                        r,
                        --dat-r
                    )
                ),
                
                SUMPRODUCT(
                    day_col,
                    adj_res
                )/limit
            )
        )
    )
)
Excel solution 8 for Dso!, proposed by Hussein SATOUR:
=MAP(
    F3:F5,
    G3:G5,
    LAMBDA(
        x,
        y,
        LET(
            a,
            SORT(
                FILTER(
                    B3:D25,
                    C3:C25=x
                ),
                1,
                -1
            ),
            v,
            INDEX(
                a,
                ,
                3
            ),
            RT,
            SCAN(
                0,
                v,
                SUM
            ),
            b,
            FILTER(
                v,
                RT<=y
            ),
            Part,
            VSTACK(
                b,
                y-SUM(
                    b
                )
            ),
            Days,
            "31/8"-INDEX(
                a,
                ,
                1
            ),
            SUM(
                IFNA(
                    Part*Days,
                    0
                )
            )/y
        )
    )
)

Solving the challenge of Dso! with Python

Python solution 1 for Dso!, proposed by Konrad Gryczan, PhD:
import pandas as pd
path = "CH-118 DSO.xlsx"
input1 = pd.read_excel(path, usecols="B:D", skiprows=1, nrows=23)
input2 = pd.read_excel(path, usecols="F:G", skiprows=1, nrows=3)
input2.columns = input2.columns.str.replace('.1', '')
test = pd.read_excel(path, usecols="H:H", skiprows=1, nrows=3)
input1 = input1.sort_values(by=['Customer', 'Date'], ascending=[True, False])
r2 = input2.merge(input1, on='Customer', how='left')
r2['days'] = pd.to_datetime('2024-08-31') - pd.to_datetime(r2['Date'])
r2['cumsum'] = r2.groupby('Customer')['Sales'].cumsum()
r2['balance_cover'] = r2.apply(lambda row: row['Sales'] if row['cumsum'] <= row['Balance'] else row['Balance'] - (row['cumsum'] - row['Sales']), axis=1)
r2 = r2[r2['balance_cover'] > 0]
r2['weighted_days'] = r2['days'].dt.days * r2['balance_cover']
result = r2.groupby('Customer').apply(lambda x: x['weighted_days'].sum() / x['balance_cover'].sum()).reset_index(name='weighted_days')
print(result["weighted_days"].equals(test["DSO (day)"])) # True

Solving the challenge of Dso! with Python in Excel

Python in Excel solution 1 for Dso!, proposed by Alejandro Campos:
df_c = pd.DataFrame(data)

df_c['Weighted Days'] = df_c['Sales'] * df_c['Days']
df_c.loc['Total'] = df_c[['Sales', 'Weighted Days']].sum()

df_c.loc['Total', 'DSO'] = df_c.loc['Total', 'Weighted Days'] / df_c.loc['Total', 'Sales']
df_c.fillna('')
Python in Excel solution 2 for Dso!, proposed by Alejandro Campos:
df1 = xl("B2:D25", headers=True)
df2 = xl("F2:G5", headers=True)
df1 = df1.sort_values(by=['Customer', 'Date'], ascending=[True, False])
merg2 = df2.merge(df1, on='Customer', how='left')
merg2['days'] = pd.to_datetime('2024-08-31') - pd.to_datetime(merg2['Date'])
def calculate_dso(group, balance):
 group['cumsum'] = group['Sales'].cumsum()
 group['balance_cover'] = group.apply(lambda row: row['Sales'] if row['cumsum'] <= balance else balance - (row['cumsum'] - row['Sales']), axis=1)
 group = group[group['balance_cover'] > 0]
 group['DSO (day)'] = group['days'].dt.days * group['balance_cover']
 return group['DSO (day)'].sum() / group['balance_cover'].sum()
result = merg2.groupby('Customer').apply(lambda x: calculate_dso(x, df2[df2['Customer'] == x.name]['Balance'].values[0])).reset_index(name='DSO (day)')
result

Solving the challenge of Dso! with R

R solution 1 for Dso!, proposed by Konrad Gryczan, PhD:
library(tidyverse)
library(readxl)
path = "files/CH-118 DSO.xlsx"
input1 = read_excel(path, range = "B2:D25")
input2 = read_excel(path, range = "F2:G5")
test = read_excel(path, range = "H2:H5")
r1 = input1 %>%
 arrange(Customer, desc(Date))
r2 = input2 %>%
 left_join(r1, by = "Customer") %>%
 mutate(days = as.Date("2024/08/31") - as.Date(Date)) %>%
 mutate(cumsum = cumsum(Sales), .by = Customer) %>%
 mutate(balance_cover = ifelse(cumsum <= Balance, Sales, Balance - (cumsum - Sales))) %>%
 filter(balance_cover > 0) %>%
 mutate(weighted_days = days * balance_cover) %>%
 summarise(weighted_days = as.numeric(sum(weighted_days) / sum(balance_cover)), .by = Customer)
all.equal(r2$weighted_days, test$`DSO (day)`, check.attributes = FALSE)
#> [1] TRUE

Solving the challenge of Dso! with Google Sheets

Google Sheets solution 1 for Dso!, proposed by Peter Krkos:
PowerQuery Solution:
https://docs.google.com/spreadsheets/d/1zR5IZLz8OT76vhaPEHfsPrw8-RDKnLyyqS49IJjdhFk/edit?gid=2037537045#gid=2037537045

Leave a Reply