Home » Custom Grouping! Part 8

Custom Grouping! Part 8

Solving Custom Grouping Part 8 challenge by Power Query, Power BI, Excel, Python and R

The stock prices for the given dates are provided in the table. Extract the highest increase and decrease in stock price either in a single day or over consecutive days with the same upward or downward trend. Upward trends are shown in green, while downward trends are shown in red. A trend is defined as a movement in the same direction for more than two consecutive dates. In this example, the highest decrease occurred between 3/1 and 4/1, while the highest increase happened between 4/1 and 8/1, both following a consistent upward trend.

📌 Challenge Details and Links
Challenge Number: 139
Challenge Difficulty: ⭐⭐⭐⭐
📥Download Sample File
📥Link to the solutions on LinkedIn

Solving the challenge of Custom Grouping! Part 8 with Power Query

Power Query solution 1 for Custom Grouping! Part 8, proposed by Omid Motamedisedeh:
let
  S0 = Excel.CurrentWorkbook(){[Name = "Table1"]}[Content][Stock price], 
  S1 = List.Accumulate(
    S0, 
    [m = {}, i = 0, s = {}], 
    (a, b) =>
      if (a[i] < 2 or ((b > List.Last(a[s])) = (a[s]{1} > a[s]{0}))) then
        [m = a[m], s = a[s] & {b}, i = a[i] + 1]
      else
        [m = a[m] & {a[s]}, s = {b}, i = 0]
  ), 
  S2 = List.Transform(S1[m] & {S1[s]}, each (List.Last(_) - _{0}) / _{0})
    & List.Transform(List.Skip(List.Positions(S0)), each S0{_} / S0{_ - 1} - 1), 
  Custom1 = #table({"Group", "Percent"}, {{"Increase", List.Max(S2)}, {"Decrease", List.Min(S2)}})
in
  Custom1
Power Query solution 2 for Custom Grouping! Part 8, proposed by Zoran Milokanović:
let
  Source = Excel.CurrentWorkbook(){[Name = "Input"]}[Content][Stock price], 
  R = List.Transform(
    List.Accumulate(
      Source, 
      {}, 
      (b, n) =>
        let
          l = List.Last(b), 
          d = if n > l{0} then "U" else "D"
        in
          {b & {{n, null, {n}}}, List.RemoveLastN(b) & {{n, d, l{2} & {n}}}}{
            Byte.From(b <> {} and (l{1} = null or l{1} = d))
          }
    ), 
    each {_{2}{0}, List.Last(_{2})}
  ), 
  T = List.Transform(
    List.Split(List.RemoveLastN(List.Skip(List.Combine(R))), 2) & R, 
    each _{1} / _{0} - 1
  ), 
  S = Table.FromColumns(
    {{"Increase", "Decrease"}, {List.Max(T), List.Min(T)}}, 
    {"Group", "Percent"}
  )
in
  S
Power Query solution 3 for Custom Grouping! Part 8, proposed by Ramiro Ayala Chávez:
let
S = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
a = Table.AddIndexColumn(S,"I"),
b = Table.RemoveRowsWithErrors(Table.AddColumn(a,"N", each a{[I]+1}[Stock price])),
c = Table.AddColumn(b,"D", each [Stock price]-[N]),
d = Table.AddColumn(c,"B", each if[D]<=0 then 1 else 0),
e = Table.Group(d,{"B"},{"G", each _},GroupKind.Local)[G],
f = List.Transform(e, each Table.FirstN(_,1)&Table.LastN(_,1)),
g = List.Transform(f, each List.Combine({[Stock price]}&{[N]})),
h = Table.FromRows(List.Transform(g, each {_{0}}&{_{3}}),{"V1","V2"}),
i = Table.AddColumn(h,"Percent", each Number.Round(([V2]-[V1])/[V1],2)),
j = Table.MaxN(i,"Percent",1)&Table.MinN(i,"Percent",1),
Sol = Table.AddColumn(j,"Group", each if[Percent]>=1 then "Increase" else "Decrease")[[V1],[V2],[Group],[Percent]]
in
Sol
Power Query solution 4 for Custom Grouping! Part 8, proposed by Alejandro Simón 🇵🇦 🇪🇸:
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
Custom1 = Source[Stock price],
Calc = List.Transform({0,1}, (k)=> 
 List.Generate(()=> [x = {Custom1{0}}, y = 1],
 each [y]<=List.Count(Custom1),
 each [y = [y]+1,
 z = {Custom1{[y]}>List.Last([x]),Custom1{[y]} Number.ToText(a{k}(List.Transform(List.Select(Calc{k}, each List.Count(_)>1), 
 each (List.Last(_)-_{0})/_{0})), "p0"))
in b,
Sol = Table.FromColumns({{"Increase", "Decrease"}, Calc2}, {"Group", "Percent"})
in
Sol
Power Query solution 5 for Custom Grouping! Part 8, proposed by Kris Jaganah:
let
  A = Excel.CurrentWorkbook(){[Name = "Table1"]}[Content], 
  B = Table.TransformColumnTypes(A, {{"Date", Int64.Type}}), 
  C = Table.AddColumn(
    B, 
    "Ans", 
    each 
      let
        a = [Stock price], 
        b = Table.SelectRows(B, (x) => x[Date] < [Date])[Stock price], 
        c = List.Transform(b, each a / _ - 1)
      in
        c
  ), 
  D = List.Combine(C[Ans]), 
  E = #table(
    type table [Group = Text.Type, Percent = Percentage.Type], 
    {{"Increase", List.Max(D)}, {"Decrease", List.Min(D)}}
  )
in
  E
Power Query solution 6 for Custom Grouping! Part 8, proposed by 🇮🇷 Navid Esmaeilzadeh اسماعیل زاده:
let
S = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
 A = Table.AddIndexColumn(S, "Index", 0, 1, Int64.Type),
 B = Table.AddColumn(A, "Diff", each List.Skip(List.Accumulate(List.FirstN(A[Stock price],[Index]),{0},(S,C)=>S&{([Stock price]-C)/C}),1)),
 C = Table.FromColumns({{Number.Round(List.Max(List.Combine(B[Diff])),2),Number.Round(List.Min(List.Combine(B[Diff])),2)},{"Increase","Dacrease"}},{"Percent","Group"}),
 D = Table.TransformColumnTypes(C,{{"Percent", Percentage.Type}})
in
 D
Power Query solution 7 for Custom Grouping! Part 8, proposed by Luke Jarych:
let
 // Load source data from the table
 Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
 StockPrices = Source[Stock price],
 CumulativeTrends = List.Combine(List.Transform({0..List.Count(StockPrices) - 1}, (i) =>
 let
 currentPrice = StockPrices{i},
 previousPrices = List.FirstN(StockPrices, i), 
 trendValues = List.Transform(previousPrices, each currentPrice / _ - 1)
 in
 trendValues
 )),
 Result = 
hashtag
#table(
 type table[Group = Text.Type, Percent = Percentage.Type],
 {
 {"Increase", List.Max(CumulativeTrends)}, // Max cumulative trend value
 {"Decrease", List.Min(CumulativeTrends)}  // Min cumulative trend value
 }
 )
in
 Result
Power Query solution 8 for Custom Grouping! Part 8, proposed by Szabolcs Phraner:
let
Source = ...,
 //To evaluate previous or next row
 Index = Table.AddIndexColumn(Source, "Index", 0, 1, Int64.Type),
//Save table to memory as it will be resused several times
 Buffer = Table.Buffer( Index ),
 Trend = Table.AddColumn(Buffer, "Group", each [ 
 isFirst = [Index] = 0,
 ComparedTo = if isFirst then Buffer{1}[Stock price] else Buffer{[Index] -1}[Stock price],
 Group = if (isFirst and [Stock price] < ComparedTo) or [Stock price] > ComparedTo then "Increase" else "Decrease"][Group],Text.Type),
/
 Percentages = Table.Group(Trend, {"Group"}, {{"Percent", each
[
 tbl =_,
 start =try Buffer{ tbl{0}[Index] -1 }[Stock price] otherwise Table.First(tbl)[Stock price] ,
 end = Table.Max(tbl,"Index")[Stock price],
 diff = end-start,
 percent = Number.Round( diff / start, 2)
] [percent]
, Percentage.Type
}},
0
),
 Min_Max_Calc = Table.Group(Percentages, {"Group"}, {{"Percent", each if Table.FirstValue(_) = "Increase" then List.Max([Percent]) else List.Min([Percent]) , Percentage.Type}})
in
 Min_Max_Calc

Solving the challenge of Custom Grouping! Part 8 with Excel

Excel solution 1 for Custom Grouping! Part 8, proposed by Bo Rydobon 🇹🇭:
=LET(
    s,
    C3:C26,
    DROP(
        GROUPBY(
            SCAN(
                0,
                TOCOL(
                    s
Excel solution 2 for Custom Grouping! Part 8, proposed by Kris Jaganah:
=LET(
    a,
    DAY(
        B3:B26
    ),
    b,
    C3:C26,
    c,
    b/IFNA(
        XLOOKUP(
            a-TOROW(
                a
            ),
            a,
            b
        ),
        b
    )-1,
    HSTACK(
        {"Inc";"Dec"}&"rease",
        VSTACK(
            MAX(
                c
            ),
            MIN(
                c
            )
        )
    )
)
Excel solution 3 for Custom Grouping! Part 8, proposed by Mark Biegert:
=LET(d,
    VSTACK(
        C3,
        C3:C26
    ),q,
    DROP(1+(DROP(
        d,
        1
    )-d)/d,
    -1),z,
     MAX(
         SCAN(
             1,
             q,
             LAMBDA(
                 a,
                 v,
                 IF(
                     v>1,
                     IFNA(
                         a*v,
                         1
                     ),
                     1
                 )
             )
         )
     )-1,zz,
     MIN(
         SCAN(
             1,
             q,
             LAMBDA(
                 a,
                 v,
                 IF(
                     v<1,
                     IFNA(
                         a*v,
                         1
                     ),
                     1
                 )
             )
         )
     )-1,VSTACK(
    {"Group",
    "Percent"},
    HSTACK(
        VSTACK(
            "Increase",
            "Decrease"
        ),
        VSTACK(
            z,
            zz
        )
    )
)
)

Solving the challenge of Custom Grouping! Part 8 with Python

Python solution 1 for Custom Grouping! Part 8, proposed by Luke Jarych:
import pandas as pd
import xlwings as xw
import re

# Import workbook range as DataFrame
wb = xw.Book(r'CH-139 Custom Grouping.xlsx')
sh = wb.sheets[0]
table1 = sh.tables['Table1']
rng1 = sh.range(table1.range.address)
df = rng1.options(pd.DataFrame, header=True, index=False, numbers=float).value

def cumulative_percentage_changes(series):
 percentage_changes = [[]] # Initialize with an empty list for the first row
 
 for i in range(1, len(series)):
 current_value = series.iloc[i]
 previous_values = series.iloc[:i]
 changes = [(current_value - prev) / prev * 100 for prev in previous_values]
 percentage_changes.append(changes)
 
 return percentage_changes

df['Cumulative Percentage Changes'] = cumulative_percentage_changes(df['Stock price'])
flattened_changes = [item for sublist in df['Cumulative Percentage Changes'] for item in sublist]

max_change = round(max(flattened_changes))
min_change = round(min(flattened_changes))

# Print the results as percentages
print(f"Increase: {max_change}%")
print(f"Decrease: {min_change}%")

Solving the challenge of Custom Grouping! Part 8 with Python in Excel

Python in Excel solution 1 for Custom Grouping! Part 8, proposed by Alejandro Campos:
df = xl("B2:C26", headers=True)
df['dI'], df['dD'] = (df['Stock price'].shift() > df['Stock price']) 
 .cumsum(), (df['Stock price'].shift() < df['Stock price']).cumsum()
df['gI'], df['gD'] = [df.groupby(g)[g].transform(
 lambda x: x if len(x) > 2 else pd.NA) for g in ['dI', 'dD']]
df['chk'] = df.apply(lambda r: r['gI'] if pd.notna(r['gI']) and pd.isna(r['gD']) 
 else r['gD'] if pd.notna(r['gD']) and pd.isna(r['gI']) 
 else min(r['gI'], r['gD']) if pd.notna(r['gI']) and pd.notna(r['gD']) 
 else pd.NA, axis=1)
df['dSP'] = df['Stock price'].diff().fillna(0)
df['mChk'] = df.groupby('chk')['dSP'].transform(
 lambda x: 1 if x.gt(0).sum() > x.lt(0).sum() else -1)
df['Grp'] = df['mChk'].map({1: 'Upward', -1: 'Downward'})
df['pct'] = df['Stock price'].pct_change().fillna(0)
df['fVal'], df['cSum'] = df.groupby('chk')['Stock price'].transform(
 'first'), df.groupby('chk')['dSP'].transform(lambda x: x.shift(-1).cumsum())
df['rtPct'] = (df['cSum'] / df['fVal']).fillna(0)
rsm = df.groupby('Grp').agg({'pct': ['min', 'max'], 'rtPct': ['min', 'max']})
res = pd.DataFrame({'Grp': ['Increase', 'Decrease'], 
 'Pct': [rsm.values.max(), rsm.values.min()]})

Solving the challenge of Custom Grouping! Part 8 with Google Sheets

Google Sheets solution 1 for Custom Grouping! Part 8, proposed by Peter Krkos:
PowerQuery solution:
https://docs.google.com/spreadsheets/d/1zR5IZLz8OT76vhaPEHfsPrw8-RDKnLyyqS49IJjdhFk/edit?gid=1817375489#gid=1817375489

Leave a Reply