The post Forecasting with seasonality in Power BI appeared first on The BIccountant.

]]>Please note, that the monthly variation I am using in this example is taken from an existing table and not derived by statistical methods like the Excel FORECAST.ETS.SEASONALITY function. Here, we simply apply an existing distribution to expected future sales, but I will cover forecasts using those statistical methods in upcoming blogposts.

Todays starting point is a request to calculate how many sales to expect until the end of the year and then distribute the expected sales according to a list of monthly %, which are all different, because there is an expected seasonality in the sales:

The total amount of expected sales for the year should be based on the sales of the past:

- Take the amount of the sales for the months, which are already closed
- Lookup the sum of % for the closed months and add them up
- Apply the rule of three to determine the expected sales of the whole year
- Allocate the difference to the remaining months, based on their monthly %

The following picture gives an overview of the situation:

So we are in May and have some sales in already, but the last closed month is April (these months add up to 1,435 sales in total). Therefore the % from the Forecast table will be taken from January until April as well (they add up to 28%). Applying the rule of three forecast the amount of 5,125 for the whole year. We will distribute the share for the months May-Dec according to the % from the forecast table.

One additional requirement is that the solution should be dynamic: As soon as a new month is closed, the amount shall be automatically updated. So the base sum that determines the level includes one more month and the months with forecast values start one month later.

My tables are connected as follows:

This DAX-measure returns the desired allocation:

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

SumForecast = | |

VAR _previousMonth = | |

CALCULATE ( | |

MAX ( 'Date'[Year Month Number] ), — Must be the column that is connected to the Forecast-table | |

REMOVEFILTERS ( 'Date' ), | |

'Date'[Date] = TODAY () | |

) – 1 | |

VAR _latestYTD = | |

CALCULATE ( | |

[YTD SumSales], | |

REMOVEFILTERS ( 'Date' ), | |

'Date'[Year Month Number] = _previousMonth | |

) | |

VAR _cumulPercentage = | |

CALCULATE ( | |

[YTD SumPercentage], | |

REMOVEFILTERS ( 'Date' ), | |

'Date'[Year Month Number] = _previousMonth | |

) | |

VAR _amountForTotalYear = | |

DIVIDE ( _latestYTD, _cumulPercentage ) | |

VAR _result = | |

SUMX ( | |

'Forecast', | |

VAR _isPlanningMonth = | |

( CALCULATE ( MAX ( 'Date'[Year Month Number] ) ) > _previousMonth ) | |

VAR _relevantYTD = | |

IF ( _isPlanningMonth, _amountForTotalYear * Forecast[ % Forecast], [SumSales] ) | |

RETURN | |

_relevantYTD | |

) | |

RETURN | |

_result |

- The first variable “_previousMonth” determines the last closed month. For simplicity-reason, we assume that this will always be the last month before today. In a later blogpost I will show a solution where this can be individually selected. Please note, that the MAX-column that is picked from the Date-table needs to have the same granularity than the Forecast-table. Therefore, I’ve picked the column over which these 2 tables are connected.
- The next variable “_latestYTD” fetches the sum of sales that have been occurred up until the previous month. I’m referencing a YTD-measure here, which follows a fairly standard definition:
CALCULATE ( [SumSales], DATESYTD ( ‘Date'[Date] ) ).
- Variable “_cumulPercentage” adds up the %-values of the closed months. Again, referenced YTD-measure has same logic than for sales above.
- Now we have all the values for the rule of three to calculate the total amount of sales of the year (“_amountForTotalYear” in row 20).
- Then comes the final calculation where we apply the %-values on the annual amount (for the open months) and simply take the actual sales amounts for the closed months. As these amounts shall be aggregated correctly on non-month intervals like year or quarter, we have to iterate over the months.
- Therefore we take the whole Forecast-table (row 24), as we need the monthly granularity and also need to pick the %-value.
- VAR “_isPlanningMonth” determines if the currently iterated month is closed or not.
- VAR “_relevantYTD” then picks the fitting value for each month: Either the actual or forecasted (_amountTotalYear multiplied by the monthly %).

- All the months’ values will then be added up, because we are using the SUMX-function here (row 23).Wa

Doing forecasts with DAX has become a very viable solution for me since the availability of a standard connector for DAX queries against Power BI datasets recently. This allows me to export my DAX calculations back into ERP- or bookkeeping systems in an automated way. Check out my next blogpost where I describe how this could be done for the example above.

In upcoming posts I will also cover how to:

- integrate more attributes into the allocation like products or cost centres
- create the %-table automatically based on previous years figures
- give an option to increase or decrease the forecast amount by certain %
- use statistical methods to create a forecast with seasonality based on historical data in Power BI

The file is available for download here: Forecast-1.pbix

Do you see other use cases that I should cover? Please let me know in the comments below.

Enjoy and stay queryious 😉

The post Forecasting with seasonality in Power BI appeared first on The BIccountant.

]]>The post Create (empty) table string from schema in Power Query appeared first on The BIccountant.

]]>The function I’m sharing here creates a string with M-code that creates an empty table with the same column names and types than the original table automatically for you. All you have to do is reference the table without errors in the function parameter.

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

let | |

fnFormatted = let | |

func = (myTable as table) => | |

let | |

#"Table1 Schema" = Table.Schema(myTable), | |

TypesList = Table.AddColumn( | |

#"Table1 Schema", | |

"TypeRecord", | |

each [Name] & "=" & [TypeName] | |

)[TypeRecord], | |

TypeRecordString = "[" & Text.Combine(TypesList, ", ") & "]", | |

TableString = "#table(type table " | |

& TypeRecordString | |

& ", {{" | |

& Text.Combine(List.Repeat({"null"}, List.Count(TypesList)), ", ") | |

& "}})" | |

in | |

TableString, | |

documentation = [ | |

Documentation.Name = " Text.CreateEmptyTableFromSchema ", | |

Documentation.Description | |

= " Creates the M-code for an empty table based on a table schema. ", | |

Documentation.LongDescription | |

= " Creates the M-code for an empty table based on a table schema (Table.Schema). ", | |

Documentation.Category = " Text.Transformations ", | |

Documentation.Source = " www.TheBIcountant.com https://wp.me/p6lgsG-2tJ . ", | |

Documentation.Version = " 1.0 ", | |

Documentation.Author = " Imke Feldmann ", | |

Documentation.Examples = { | |

[Description = " ", | |

Code = | |

" let | |

myTable = #table( type table [myText = Text.Type, myNumber = Int64.Type, myDate = Date.Type], | |

// myText| myNumber| myDate| | |

{//——-|———|——————| | |

{ ""A"", 10, #date(2022, 01, 01) } } ), | |

FunctionCall = fnText_CreateEmptyTableFromSchema( myTable ) | |

in | |

FunctionCall " | |

, | |

Result = " #table(type table [myText=Text.Type, myNumber=Int64.Type, myDate=Date.Type], {{null, null, null}}) " | |

]}] | |

in | |

Value.ReplaceType(func, Value.ReplaceMetadata(Value.Type(func), documentation)) | |

in | |

fnFormatted |

Copy the code above and paste it into the advanced editor in Power Query.

It will return a function that asks for 1 parameter: The table that shall be replicated. It will then return the M-code that you can use in the “otherwise” branch of your error handling.

It creates the table string from schema, using the Table.Schema M-function (row 5). It then adds a column that reads out column names and types from the schema table to create the string for the type record (row 6-10). Then it concatenates everything to create the M-code for an empty table like so:

`#table(type table [myText=Text.Type, myNumber=Int64.Type, myDate=Date.Type], {{null, null, null}})`

Enjoy & stay queryious 😉

The post Create (empty) table string from schema in Power Query appeared first on The BIccountant.

]]>The post Fill values to the right in Power Query and Power BI appeared first on The BIccountant.

]]>

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

let func = | |

(SourceTable as table, FillColumns, optional FillLeft) => | |

let | |

Mode = if FillLeft = null then Table.FillDown else Table.FillUp, | |

#"Added Custom" = Table.AddColumn( | |

SourceTable, | |

"Custom", | |

each Table.FromRows( | |

{ | |

Function.Invoke( | |

Mode, | |

{ | |

Table.FromColumns( | |

{Record.FieldValues(Record.SelectFields(_, FillColumns))} | |

), | |

{"Column1"} | |

} | |

)[Column1] | |

}, | |

FillColumns | |

) | |

), | |

#"Removed Columns" = Table.RemoveColumns(#"Added Custom", FillColumns), | |

#"Expanded Custom" = Table.ExpandTableColumn(#"Removed Columns", "Custom", FillColumns), | |

#"Reordered Columns" = Table.ReorderColumns( | |

#"Expanded Custom", | |

Table.ColumnNames(SourceTable) | |

) | |

in | |

#"Reordered Columns" , | |

documentation = [ | |

Documentation.Name = " Table.FillRight.pq ", | |

Documentation.Description = " Returns a table from the <code>SourceTable</code> specified where the value of a previous cell is propagated to the null-valued cells right from the <code>FillColumns</code> specified. ", | |

Documentation.LongDescription = " Returns a table from the <code>SourceTable</code> specified where the value of a previous cell is propagated to the null-valued cells right from the <code>FillColumns</code> specified. Optional <code>third parameter</code> fills to the left instead. ", | |

Documentation.Category = " Table ", | |

Documentation.Source = " www.TheBIcountant.com – hhttps://wp.me/p6lgsG-2t1 ", | |

Documentation.Version = " 1.0 ", | |

Documentation.Author = " Imke Feldmann ", | |

Documentation.Examples = {[Description = " ", | |

Code = " let | |

SourceTable = #table( type table [Column1 = Text.Type, H1 = Text.Type, H2 = Text.Type, H3 = Text.Type], | |

// Column1| H1| H2| H3| | |

{//—————|———|—————–|——————| | |

{ ""BalanceSheet"", ""Assets"", null, null }, | |

{ ""BalanceSheet"", null, ""Current Assets"", null }, | |

{ ""BalanceSheet"", null, null, ""Current Asset 1"" } } ) , | |

FillColumns = {""H1"", ""H2"", ""H3""}, | |

FunctionCall = fnSampleFunction(SourceTable, FillColumns) | |

in | |

FunctionCall ", | |

Result = " #table( type table [Column1 = Text.Type, H1 = Any.Type, H2 = Any.Type, H3 = Any.Type], | |

// Column1| H1| H2| H3| | |

{//—————|———|—————–|——————| | |

{ ""BalanceSheet"", ""Assets"", ""Assets"", ""Assets"" }, | |

{ ""BalanceSheet"", null, ""Current Assets"", ""Current Assets"" }, | |

{ ""BalanceSheet"", null, null, ""Current Asset 1"" } } ) | |

"]}] | |

in | |

Value.ReplaceType(func, Value.ReplaceMetadata(Value.Type(func), documentation)) | |

The first function argument takes the table you want to apply the function on. The second argument is the list of column names that shall be filled up into empty values to the right. In the example in the function documentation, this is: {“H1”, “H2”, “H3”}. The curly brackets define a list object in Power Query and its list elements must be put in quotes if they shall represent strings. So here the columns H1, H2 and H3 are included.

An optional 3rd argument can be used to fill to the left instead. You can fill in any value there, so once it is used, the fill will work to the left instead.

First the function will add a custom column to the existing table where it selects the values of the selected fill-columns from the current record/row (see row 14 in the function above):

`Record.SelectFields(_, FillColumns)`

Then it will fetch the values from these selected column with the Record.FieldValues-function that has been wrapped around the selection (still row 14):

`{Record.FieldValues(Record.SelectFields(_, FillColumns))}`

This will return a list of values. Now the target is to bring this into a shape where I can use the existing function Table.FillDown or Table.FillUp to fill in empty values. Therefore I transform this list into a table. This happens in row 13 with the Table.FromColumns-function.

This returns a table with one column named “Column1”. On it, we can now do the fill-operations. The direction on which to fill is determined by the 3rd function argument. In there you can change the default right-fill to fill down instead. The logic for this is handled in row 4. There the variable “Mode” will return the selected function. Then in row 10 the function Function.Invoke can conditionally execute the Mode-function. Rows 11 – 17 provide the arguments for it. As a result, I get a one-column table with all values filled in.

Now I only have to tip this by 90° to bring the rows into columns and restore the original column names. Therefore I use the Table.FromRows function in row 8. It allows me to determine the column names in one go (row 20).

Then I remove the original columns (row 23), expand my newly created columns (row 24) and reorder the columns to its original shape (row 25).

Enjoy and stay queryious 😉

The post Fill values to the right in Power Query and Power BI appeared first on The BIccountant.

]]>The post Why are Power Platform Dataflows adding steps automatically at the end? appeared first on The BIccountant.

]]>It will be a step that replaces errors on certain columns followed by a step that transforms these columns into text.

This will happen for every untyped column of the table that’s going to be loaded. So unlike Power BI datasets, who accept untyped columns, dataflows don’t.

As text is the format that most other simple types can also be stored in without loosing their content, this is the format in which untyped columns will be stored in. But before that, all errors will automatically be removed, because these wouldn’t be translated into text.

So be sure to apply the types to your columns as you want them to be. Otherwise you will risk an automatic error-removal in your queries, that could cause trouble at the end.

Enjoy & stay queryious 😉

The post Why are Power Platform Dataflows adding steps automatically at the end? appeared first on The BIccountant.

]]>The post TRIMMEAN function for Power Query and Power BI appeared first on The BIccountant.

]]>

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

let func = | |

// fnTRIMMEAN | |

(array, percent) => | |

let | |

percentage = | |

if percent > 1 or percent < 0 | |

then error Error.Record("Percentage must be between 0 and 1 (100%)") | |

else percent, | |

Source = List.Buffer( List.Sort(array, Order.Ascending) ), | |

CountOfTotalRows = List.Count( Source ), | |

CutOffAtEachSide = Number.RoundDown((percentage * CountOfTotalRows) / 2), | |

RelevantRange = List.Range( Source, CutOffAtEachSide, CountOfTotalRows – CutOffAtEachSide * 2), | |

Result = List.Average( RelevantRange) | |

in | |

Result , | |

documentation = [ | |

Documentation.Name = " Xls.TRIMMEAN.pq ", | |

Documentation.Description = " Returns the mean of the interior of a data set. TRIMMEAN calculates the mean taken by excluding a percentage of data points from the top and bottom tails of a data set. ", | |

Documentation.LongDescription = " Returns the mean of the interior of a data set. TRIMMEAN calculates the mean taken by excluding a percentage of data points from the top and bottom tails of a data set. https://support.microsoft.com/en-us/office/trimmean-function-d90c9878-a119-4746-88fa-63d988f511d3?ui=en-us&rs=en-us&ad=us ", | |

Documentation.Category = " Xls.Statistical ", | |

Documentation.Source = " www.TheBIccountant.com ", | |

Documentation.Version = " 1.0 ", | |

Documentation.Author = " Imke Feldmann ", | |

Documentation.Examples = {[Description = " ", | |

Code = " ", | |

Result = " "]}] | |

in | |

Value.ReplaceType(func, Value.ReplaceMetadata(Value.Type(func), documentation)) |

It has the same function parameters than the Excel-function:

TRIMMEAN(array, percent)

The TRIMMEAN function syntax has the following arguments:

**Array**Required. The array or range of values to trim and average.-
**Percent**Required. The fractional number of data points to exclude from the calculation. For example, if percent = 0.2, 4 points are trimmed from a data set of 20 points (20 x 0.2): 2 from the top and 2 from the bottom of the set.

If you are interested in more Power Query functions that replicate Excel functions who haven’t made it into the M-language (yet?) please check out this collection.

Enjoy and stay queryious 😉

The post TRIMMEAN function for Power Query and Power BI appeared first on The BIccountant.

]]>The post Excel WEEKNUM function for Power Query appeared first on The BIccountant.

]]>As this is also a function that has many regional options, I was lucky to find an algorithm that I could use for its main part here: M functions to convert between ISO 8601 Week & Year ⇄ dates (e.g., `2014-12-29`

⇄ `"2015-W01-1"`

) (github.com)

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

let func = | |

(Date as date, optional Return_type as number) => | |

let | |

// For a detailled description about the options of the Return_types see the official documentation: | |

// https://support.microsoft.com/en-us/office/weeknum-function-e5c43a03-b4ab-426c-b411-b18c13c75340 | |

// PQ native Date.WeekFromYear starts to count from 0(Sunday) to 6(Saturday) as opposed to Excel from 1(Sunday) to 7(Saturday) | |

ConvertedNumber = | |

if Return_type = null then | |

0 | |

else | |

Record.Field( | |

[1 = 0, 2 = 1, 11 = 1, 12 = 2, 13 = 3, 14 = 4, 15 = 5, 16 = 6, 17 = 0, 21 = 21], | |

Text.From(Return_type) | |

), | |

IsoWeek = // this function comes from r-k-b on Github: https://gist.github.com/r-k-b/18d898e5eed786c9240e3804b167a5ca | |

let | |

getDayOfWeek = (d as date) => | |

let | |

result = 1 + Date.DayOfWeek(d, Day.Monday) | |

in | |

result, | |

getNaiveWeek = (inDate as date) => | |

let | |

// monday = 1, sunday = 7 | |

weekday = getDayOfWeek(inDate), | |

weekdayOfJan4th = getDayOfWeek(#date(Date.Year(inDate), 1, 4)), | |

ordinal = Date.DayOfYear(inDate), | |

naiveWeek = Number.RoundDown((ordinal – weekday + 10) / 7) | |

in | |

naiveWeek, | |

thisYear = Date.Year(Date), | |

priorYear = thisYear – 1, | |

nwn = getNaiveWeek(Date), | |

lastWeekOfPriorYear = getNaiveWeek(#date(priorYear, 12, 28)), | |

// http://stackoverflow.com/a/34092382/2014893 | |

lastWeekOfThisYear = getNaiveWeek(#date(thisYear, 12, 28)), | |

weekYear = | |

if nwn < 1 then | |

priorYear | |

else if nwn > lastWeekOfThisYear then | |

thisYear + 1 | |

else | |

thisYear, | |

weekNumber = | |

if nwn < 1 then | |

lastWeekOfPriorYear | |

else if nwn > lastWeekOfThisYear then | |

1 | |

else | |

nwn | |

in | |

Number.RoundDown(weekNumber), | |

Default = Date.WeekOfYear(Date, ConvertedNumber), | |

Result = if Return_type = 21 then IsoWeek else Default | |

in | |

Result , | |

documentation = [ | |

Documentation.Name = " Xls.WEEKNUM.pq ", | |

Documentation.Description = " Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. Equivalent of the YEARFRAC-Function in Excel. ", | |

Documentation.LongDescription = " Returns the week number of a specific date. For example, the week containing January 1 is the first week of the year, and is numbered week 1. There are two systems used for this function: | |

System 1 The week containing January 1 is the first week of the year, and is numbered week 1. | |

System 2 The week containing the first Thursday of the year is the first week of the year, and is numbered as week 1. This system is the methodology specified in ISO 8601, which is commonly known as the European week numbering system. | |

Equivalent of the YEARFRAC-Function in Excel. ", | |

Documentation.Category = " Xls.Date ", | |

Documentation.Source = " www.TheBIcountant.com – https://wp.me/p6lgsG-2ta . ", | |

Documentation.Version = " 1.0 ", | |

Documentation.Author = " Imke Feldmann ", | |

Documentation.Examples = {[Description = " ", | |

Code = " let | |

Serial_number = #date(2012, 3, 9) , | |

Return_type = 2, | |

FunctionCall = Xls_WEEKNUM(Serial_number, Return_type) | |

in | |

FunctionCall ", | |

Result = " 11 | |

"]}] | |

in | |

Value.ReplaceType(func, Value.ReplaceMetadata(Value.Type(func), documentation)) |

The function takes the same parameters as its Excel equivalent. So this is just a copy for your convenient reference:

WEEKNUM(serial_number,[return_type])

The WEEKNUM function syntax has the following arguments:

**Serial_number**Required. A date within the week. Dates should be entered by using the DATE function, or as results of other formulas or functions. For example, use DATE(2008,5,23) for the 23rd day of May, 2008. Problems can occur if dates are entered as text.**Return_type**Optional. A number that determines on which day the week begins. The default is 1.

Return_type |
Week begins on |
System |
---|---|---|

1 or omitted | Sunday | 1 |

2 | Monday | 1 |

11 | Monday | 1 |

12 | Tuesday | 1 |

13 | Wednesday | 1 |

14 | Thursday | 1 |

15 | Friday | 1 |

16 | Saturday | 1 |

17 | Sunday | 1 |

21 | Monday | 2 |

If you are interested in more Power Query functions that replicate Excel functions who haven’t made it into the M-language (yet?) please check out this collection.

Enjoy and stay queryious 😉

The post Excel WEEKNUM function for Power Query appeared first on The BIccountant.

]]>The post Excel YEARFRAC function for Power Query appeared first on The BIccountant.

]]>

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

let func = | |

let | |

//Algo source: https://lists.oasis-open.org/archives/office-formula/200806/msg00039.html | |

FIsLeapYear = (Year) => | |

if Number.Mod(Year, 4) > 0 then | |

false | |

else if Number.Mod(Year, 100) > 0 then | |

true | |

else if Number.Mod(Year, 400) = 0 then | |

true | |

else | |

false, | |

FIsEndOfMonth = (Day, Month, Year) => | |

if List.Contains({1, 3, 5, 7, 8, 10, 12}, Day) then | |

31 | |

else if List.Contains({4, 6, 9, 11}, Day) then | |

30 | |

else if FIsLeapYear(Year) then | |

29 | |

else | |

28, | |

Days360 = (StartYear, EndYear, StartMonth, EndMonth, StartDay, EndDay) => | |

((EndYear – StartYear) * 360) + ((EndMonth – StartMonth) * 30) + (EndDay – StartDay), | |

TmpDays360Nasd = (StartDate, EndDate, Method, UseEom) => | |

let | |

StartDay = Date.Day(StartDate), | |

StartMonth = Date.Month(StartDate), | |

StartYear = Date.Year(StartDate), | |

EndDay = Date.Day(EndDate), | |

EndMonth = Date.Month(EndDate), | |

EndYear = Date.Year(EndDate), | |

Select = [ | |

EndDay = | |

if (EndMonth = 2 and FIsEndOfMonth(EndDay, EndMonth, EndYear)) | |

and ( | |

(StartMonth = 2 and FIsEndOfMonth(StartDay, StartMonth, StartYear)) | |

or Method | |

= 3 | |

) | |

then | |

30 | |

else if EndDay = 31 and (StartDay >= 30 or Method = 3) then | |

30 | |

else | |

EndDay, | |

StartDay = | |

if StartDay = 31 then | |

30 | |

else if ( | |

UseEom | |

= 2 and StartMonth | |

= 2 and FIsEndOfMonth(StartDay, StartMonth, StartYear) | |

) | |

then | |

30 | |

else | |

StartDay | |

], | |

TmpDays360Nasd = Days360(StartYear, EndYear, StartMonth, EndMonth, StartDay, EndDay) | |

in | |

#"TmpDays360Nasd", | |

TmpDays360Euro = (StartDate, EndDate) => | |

let | |

StartDay = Date.Day(StartDate), | |

StartMonth = Date.Month(StartDate), | |

StartYear = Date.Year(StartDate), | |

EndDay = Date.Day(EndDate), | |

EndMonth = Date.Month(EndDate), | |

EndYear = Date.Year(EndDate), | |

StartDay_ = if (StartDay = 31) then 30 else StartDay, | |

EndDay_ = if (EndDay = 31) then 30 else EndDay, | |

TmpDays360Euro = Days360(StartYear, EndYear, StartMonth, EndMonth, StartDay_, EndDay_) | |

in | |

TmpDays360Euro, | |

TmpDiffDates = (StartDate, EndDate, Basis) => | |

if Basis = 0 then | |

TmpDays360Nasd(StartDate, EndDate, 0, true) | |

else if List.Contains({1, 2, 3}, Basis) then | |

Duration.Days(EndDate – StartDate) | |

else | |

TmpDays360Euro(StartDate, EndDate), | |

TmpCalcAnnualBasis = (StartDate, EndDate, Basis) => | |

if List.Contains({0, 2, 4}, Basis) then | |

360 | |

else if Basis = 3 then | |

365 | |

else | |

let | |

StartDay = Date.Day(StartDate), | |

StartMonth = Date.Month(StartDate), | |

StartYear = Date.Year(StartDate), | |

EndDay = Date.Day(EndDate), | |

EndMonth = Date.Month(EndDate), | |

EndYear = Date.Year(EndDate), | |

TmpCalcAnnualBasis_ = | |

if (StartYear = EndYear) then | |

if FIsLeapYear(StartYear) then 366 else 365 | |

else if ((EndYear – 1) = StartYear) | |

and ( | |

(StartMonth > EndMonth) | |

or ((StartMonth = EndMonth) and StartDay >= EndDay) | |

) | |

then | |

if FIsLeapYear(StartYear) then | |

if StartMonth < 2 or (StartMonth = 2 and StartDay <= 29) then | |

366 | |

else | |

365 | |

else if FIsLeapYear(EndYear) then | |

if EndMonth > 2 or (EndMonth = 2 and EndDay = 29) then 366 else 365 | |

else | |

365 | |

else | |

List.Accumulate( | |

{StartYear .. EndYear}, | |

0, | |

(state, current) => | |

if FIsLeapYear(current) then state + 366 else state + 365 | |

), | |

TmpCalcAnnualBasis__ = TmpCalcAnnualBasis_ / (EndYear – StartYear + 1) | |

in | |

TmpCalcAnnualBasis__, | |

Result = (StartDate, EndDate, Basis_) => | |

let | |

Basis = if Basis_ = null then 0 else Basis_, | |

nNumerator = TmpDiffDates(StartDate, EndDate, Basis), | |

nDenom = TmpCalcAnnualBasis(StartDate, EndDate, Basis), | |

TmpYearFrac = nNumerator / nDenom | |

in | |

TmpYearFrac | |

in | |

Result , | |

documentation = [ | |

Documentation.Name = " Xls.YEARFRAC.pq ", | |

Documentation.Description = " Calculates the fraction of the year represented by the number of whole days between two dates (the start_date and the end_date). Equivalent of the YEARFRAC-Function in Excel. ", | |

Documentation.LongDescription = " Calculates the fraction of the year represented by the number of whole days between two dates (the start_date and the end_date). For instance, you can use YEARFRAC to identify the proportion of a whole year's benefits, or obligations to assign to a specific term. Equivalent of the YEARFRAC-Function in Excel. ", | |

Documentation.Category = " Xls.Date ", | |

Documentation.Source = " www.TheBIcountant.com – https://wp.me/p6lgsG-2t4 . ", | |

Documentation.Version = " 1.0 ", | |

Documentation.Author = " Imke Feldmann ", | |

Documentation.Examples = {[Description = " ", | |

Code = " let | |

Start_date = #date(2012,1,1) , | |

End_date = #date(2012, 7, 30), | |

Basis = 3, | |

FunctionCall = Xls_YEARFRAC(Start_date, End_date, Basis) | |

in | |

FunctionCall ", | |

Result = " 0.578082191780821 | |

"]}] | |

in | |

Value.ReplaceType(func, Value.ReplaceMetadata(Value.Type(func), documentation)) |

The Excel YEARFRAC function for Power Query has the same parameters than its Excel equivalent:

YEARFRAC(start_date, end_date, [basis])

The YEARFRAC function syntax has the following arguments:

**Start_date**Required. A date that represents the start date.**End_date**Required. A date that represents the end date.**Basis**Optional. The type of day count basis to use.

Basis |
Day count basis |
---|---|

0 or omitted | US (NASD) 30/360 |

1 | Actual/actual |

2 | Actual/360 |

3 | Actual/365 |

4 |
European 30/360 |

If you are interested in more Power Query functions that replicate Excel functions who haven’t made it into the M-language (yet?) please check out this collection.

Enjoy and stay queryious 😉

The post Excel YEARFRAC function for Power Query appeared first on The BIccountant.

]]>The post Excel WORKDAY equivalent in Power Query and Power BI appeared first on The BIccountant.

]]>(If you came here for the NETWORKDAYS function instead, please check out this post: Date.Networkdays function for Power Query and Power BI – (thebiccountant.com) )

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

let func = | |

(StartDate as date, Days as number, optional Holidays as list) => | |

let | |

/* Debug parameters | |

StartDate = #date(2008, 10, 1), | |

Days = 151, | |

//Holidays = {#date(2008,11,26), #date(2008,12,4), #date(2009,1,21)}, | |

*/ | |

Holidays_ = if Holidays = null then 0 else List.Count(Holidays), | |

// Create a list of days that span the max possible period | |

ListOfDates = | |

if Days >= 0 then | |

List.Dates( | |

StartDate, | |

Number.RoundUp((Days + Holidays_) * (7 / 5) + 2, 0), | |

#duration(1, 0, 0, 0) | |

) | |

else | |

let | |

EarliestStartDate = Date.From( | |

Number.From( | |

Date.AddDays(StartDate, Number.RoundUp((Days – Holidays_) * (7 / 5) – 2, 0)) | |

) | |

), | |

Result = List.Dates( | |

EarliestStartDate, | |

Number.From(StartDate – EarliestStartDate), | |

#duration(1, 0, 0, 0) | |

) | |

in | |

Result, | |

// if the optional Holidays parameter is used: Keep only those dates in the list that don't occur in the list of Holidays; | |

// otherwise continue with previous table | |

DeleteHolidays = if Holidays = null then ListOfDates else List.Difference(ListOfDates, Holidays), | |

// Select only the first 5 days of the week | |

// The 1 in the 2nd parameter of Date.DayOfWeek makes sure that Monday will be taken as first day of the week | |

DeleteWeekends = List.Select(DeleteHolidays, each Date.DayOfWeek(_, 1) < 5), | |

// Count the number of days (items in the list) | |

CountDays = | |

if Days >= 0 then | |

DeleteWeekends{Days} | |

else | |

DeleteWeekends{List.Count(DeleteWeekends) + Days}, | |

// CountDays = if Days >= 0 then List.Last(DeleteHolidays) else List.First(DeleteHolidays), | |

Result = if CountDays = null then StartDate else CountDays | |

in | |

Result , | |

documentation = [ | |

Documentation.Name = " Xls_WORKDAY ", | |

Documentation.Description = " Returns a number that represents a date that is the indicated number of working days before or after a date (the starting date). ", | |

Documentation.LongDescription = " Returns a number that represents a date that is the indicated number of working days before or after a date (the starting date). Working days exclude weekends and any dates identified as holidays. ", | |

Documentation.Category = " Xls.Date ", | |

Documentation.Source = " www.TheBIcountant.com – https://wp.me/p6lgsG-2sW ", | |

Documentation.Version = " 1.0 ", | |

Documentation.Author = " Imke Feldmann ", | |

Documentation.Examples = {[Description = " ", | |

Code = " let | |

StartDate = #date(2008, 10, 1), | |

Days = 151, | |

Holidays = {#date(2008,11,26), #date(2008,12,4), #date(2009,1,21)}, | |

Result = Xls_WORKDAY(StartDate, Days, Holidays) | |

in | |

Result ", | |

Result = " #date(2009,5,5) | |

"]}] | |

in | |

Value.ReplaceType(func, Value.ReplaceMetadata(Value.Type(func), documentation)) |

Click on “view raw” down right in the function code above, copy the code, create a new query in Power Query, open the advanced editor and replace everything in there with the copied code. Name this query “Xls_WORKDAY”.

This function’s parameters work just like the Excel function:

- StartDate: A date field as your reference
- Days: A number field that defines how many days forward or backwards you want to go. (negative values go backwards)
- optional holidays: A list of optional holiday dates. If you want to use the UI that the function has created, your holidays must reside in a column of an existing table, that you can reference within the dialogue.

Enjoy and stay queryious 😉

The post Excel WORKDAY equivalent in Power Query and Power BI appeared first on The BIccountant.

]]>The post Transforming multiple columns at once with reference to existing column in Power Query appeared first on The BIccountant.

]]>A very easy way to do such a task is to unpivot all columns that shall be transformed, do the transformation (once) and then pivot back. But this will only work with small datasets or datasets with little transformation in a performant way.

So my next approach to was to apply the Table.ReplaceValue-method from the before mentioned article and use it in a List.Accumulate function to be recursively applied to all affected columns. But that turned out to be very slow as well. At the end, I used a “drop and recreate”-approach that runs much faster:

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

let func = | |

// fnTransformMultipleColumns | |

let | |

fnTransformMultipleColumns = ( | |

SourceTable as table, | |

FieldList, | |

TransformationFunction, | |

optional FunctionArgumentsList, | |

optional newType | |

) => | |

let | |

FunctionArguments = if FunctionArgumentsList = null then {} else FunctionArgumentsList, | |

Source = Table.AddColumn( | |

SourceTable, | |

"**tempColumn**", | |

each Record.FromList( | |

List.Transform( | |

FieldList, | |

(l) => | |

Function.Invoke( | |

TransformationFunction, | |

{Record.Field(_, l)} & {_} & FunctionArguments | |

) | |

), | |

FieldList | |

) | |

), | |

RemoveOldColumns = Table.RemoveColumns(Source, FieldList), | |

ExpandNewColumns = Table.ExpandRecordColumn( | |

RemoveOldColumns, | |

"**tempColumn**", | |

FieldList | |

), | |

RestoreType = Value.ReplaceType( | |

Table.ReorderColumns(ExpandNewColumns, Table.ColumnNames(SourceTable)), | |

Value.Type(SourceTable) | |

), | |

ApplyNewTypes = Table.TransformColumnTypes( | |

ExpandNewColumns, | |

List.Transform(FieldList, each {_, newType}) | |

), | |

Result = | |

if Value.Type(newType) = type type then | |

ApplyNewTypes | |

else if newType = null or newType = true then | |

RestoreType | |

else | |

ExpandNewColumns | |

in | |

Result | |

in | |

fnTransformMultipleColumns , | |

documentation = [ | |

Documentation.Name = " Table_TransformMultipleColumns ", | |

Documentation.Description = " Transforms multiple columns of a <code>SourceTable</code> using the specified <code>TransformationFunction</code>. ", | |

Documentation.LongDescription = " Transforms multiple columns (defined in <code>FieldList</code>) of a <code>SourceTable</code> using the specified <code>TransformationFunction</code>. Optional parameters allow to pass addional function arguments and define a new type. ", | |

Documentation.Category = " Table ", | |

Documentation.Source = " www.TheBIcountant.com ", | |

Documentation.Version = " 1.0 ", | |

Documentation.Author = " Imke Feldmann ", | |

Documentation.Examples = {[Description = " ", | |

Code = " let | |

SourceTable = #table( type table [Product = Text.Type, Rate = Number.Type, Sales = Int64.Type, CoS = Int64.Type], | |

// Product| Rate| Sales| CoS| | |

{//——–|—–|——|—-| | |

{ ""A"", 1.2, 100, 40 }, | |

{ ""B"", 0.9, 200, 70 } } ), | |

FieldList = {""Sales"", ""CoS""}, | |

TransformationFunction = (currentItem, _) => currentItem * _[Rate], | |

FunctionCall = Table_TransformMultipleColumns(SourceTable, FieldList, TransformationFunction) | |

in | |

FunctionCall ", | |

Result = " #table( type table [Product = Text.Type, Rate = Number.Type, Sales = Int64.Type, CoS = Int64.Type], | |

// Product| Rate| Sales| CoS| | |

{//——–|—–|——|—-| | |

{ ""A"", 1.2, 120, 48 }, | |

{ ""B"", 0.9, 180, 63 } } ) | |

"]}] | |

in | |

Value.ReplaceType(func, Value.ReplaceMetadata(Value.Type(func), documentation)) |

- SourceTable: Is the table you want to apply this function on
- FieldList: Is a list of column names that shall be transformed
- TransformationFunction: The function that shall be applied to the columns from FieldList. This function can have as many parameters as you need, but the first the first 2 parameters are mandatory and must hold these 2 items: The first parameter must represent the item to be transformed (one of the columns from FieldList) and the second parameter must represent the current record/row. From there on you are free to add further parameters that you want to use in your function. (Example see next paragraph)
- optional FunctionArgumentsList: If your TransformationFunction uses additional parameters, you must pass them in here as a list.
- optional newType: By default, the function will automatically restore the same types for the transformed column that they had before. But if the column transformation shall also change the type, you can define it here. Just specify a type.

Starting from a table that has Sales and CoS, these columns shall be multiplied by the values in column “Rate”:

So for the first function parameter, you reference this table. For the second function parameter, you would pass in this list of column names: {“Sales”, “CoS”}.

The TransformationFunction for the 3rd argument for this will look like so:

`(currentItem, _) => currentItem * _[Rate]`

It has 2 arguments: “currentItem” stands for the columns that shall be transformed: “Sales” and “CoS” and the underscore “_” stands for the current record. So every column to be transformed will be multiplied by the value from column “Rate” within the current row.

I you paste the function code from the GitHub sample above into the advanced editor window of a blank query, you will see a sample code for this function that you can paste into a new query and explore all function arguments in separate steps.

Enjoy & stay queryious 😉

The post Transforming multiple columns at once with reference to existing column in Power Query appeared first on The BIccountant.

]]>The post Excel NORM.INV function in Power Query and Power BI appeared first on The BIccountant.

]]>The Excel NORM.INV function returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. So unlike the NORM.DIST function, that returns the probability of a threshold value to occur under the normal distribution (in CDF mode), this function returns the threshold value that matches a given probability.

Again, the parameters of this function fully match the Excel function syntax. Unfortunately I still don’t know how Excel does the exact calculation for it, so I’m using another approximation that I’ve found on the web:

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.

Learn more about bidirectional Unicode characters

let func = | |

(probability as number, mean as number, standard_dev as number) as number => | |

let | |

// Source for NormalCDFInverse: https://www.johndcook.com/blog/normal_cdf_inverse/ | |

//StdDev = 1.5, | |

//Mean = 2, | |

p = probability, | |

RationalApproximation = (t as number) => | |

[c = {2.515517, 0.802853, 0.010328}, d = {1.432788, 0.189269, 0.001308}, return | |

= t – ((c{2} * t + c{1}) * t + c{0}) / | |

(((d{2} * t + d{1}) * t + d{0}) * t + 1)][return], | |

NormalCDFInverse = if (p < 0.5) | |

then – RationalApproximation(Number.Sqrt(- 2 * Number.Log(p))) | |

else RationalApproximation(Number.Sqrt(- 2 * Number.Log(1 – p))), | |

DenormCDFInverse = NormalCDFInverse * standard_dev + mean | |

in | |

DenormCDFInverse , | |

documentation = [ | |

Documentation.Name = " Xls.NORMINV.pq ", | |

Documentation.Description = " Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. ", | |

Documentation.LongDescription = " Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation. https://support.microsoft.com/en-us/office/norm-inv-function-54b30935-fee7-493c-bedb-2278a9db7e13 ", | |

Documentation.Category = " Xls.Statistical ", | |

Documentation.Source = " www.TheBIccountant.com ", | |

Documentation.Version = " 1.0 ", | |

Documentation.Author = " Imke Feldmann ", | |

Documentation.Examples = {[Description = " ", | |

Code = " ", | |

Result = " "]}] | |

in | |

Value.ReplaceType(func, Value.ReplaceMetadata(Value.Type(func), documentation)) |

The syntax for the NORM.INV function in Power Query is identical to the Excel syntax:

- probability under the normal distribution
- mean is the arithmetic mean of the distribution
- standard_dev holds the Standard Deviation of the distibution

If you want to use this function to generate random numbers along the normal distribution curve, I also recommend to check out Sandeep Pavars version here.

Enjoy and stay queryious 😉

The post Excel NORM.INV function in Power Query and Power BI appeared first on The BIccountant.

]]>