src/applied.ps1
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Scope = 'Function', Target = 'Get-Extremum')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Scope = 'Function', Target = 'Get-LogisticSigmoid')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Scope = 'Function', Target = 'Get-Mean')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Scope = 'Function', Target = 'Get-Permutation')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Scope = 'Function', Target = 'Get-Sum')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Scope = 'Function', Target = 'Invoke-Imputation')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '', Scope = 'Function', Target = 'Find-LargestMoveable')] Param() function ConvertTo-Degree { <# .SYNOPSIS Convert radians to degrees #> [CmdletBinding()] [Alias('toDegree')] [OutputType([Double])] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [Double] $Radians ) Process { ($Radians * (180 / [Math]::Pi)) % 360 } } function ConvertTo-Radian { <# .SYNOPSIS Convert degrees to radians #> [CmdletBinding()] [Alias('toRadian')] [OutputType([Double])] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [Double] $Degrees ) Process { ($Degrees % 360) * ([Math]::Pi / 180) } } function Get-Covariance { <# .SYNOPSIS Return covariance of two discrete uniform random variables .DESCRIPTION Covariance measures the total variation of two random variables from their expected values. Using covariance, we can only gauge the direction of the relationship (whether the variables tend to move in tandem or show an inverse relationship). However, it does not indicate the strength of the relationship, nor the dependency between the variables. To measure the strength and relationship between variables, calculate correlation. .PARAMETER Sample Divide by ($Data.Count - 1) instead of $Data.Count. Reasearch "degrees of freedom" for more information. May also be referred to as "unbiased". .EXAMPLE $X = 1692,1978,1884,2151,2519 $Y = 68,102,110,112,154 $X,$Y | Get-Covariance -Sample #> [CmdletBinding()] [Alias('covariance')] Param( [Parameter(Position = 0, ValueFromPipeline = $True)] [Array] $Data, [Switch] $Sample ) End { $Values = if ($Input.Count -eq 2) { $Input } else { $Data } $X, $Y = $Values $MeanX = Get-Mean $X $MeanY = Get-Mean $Y $ResidualX = $X | ForEach-Object { $_ - $MeanX } $ResidualY = $Y | ForEach-Object { $_ - $MeanY } $Values = $ResidualX, $ResidualY | Invoke-Zip | ForEach-Object { $_[0] * $_[1] } if ($Sample) { ($Values | Get-Sum) / ($Values.Count - 1) } else { Get-Mean $Values } } } function Get-Extremum { <# .SYNOPSIS Function to return extremum (maximum or minimum) of an array of numbers .EXAMPLE $Maximum = 1,2,3,4,5 | Get-Extremum -Max # 5 .EXAMPLE $Minimum = 1,2,3,4,5 | Get-Extremum -Min # 1 #> [CmdletBinding()] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [Array] $InputObject, [Alias('Max')] [Switch] $Maximum, [Alias('Min')] [Switch] $Minimum ) Begin { function Invoke-GetExtremum { Param( [Parameter(Position = 0)] [Array] $Values ) if ($Values.Count -gt 0) { if ($Values[0] | Test-Match -Date) { $Sorted = $Values | Sort-Object { [System.DateTime]::Parse($_) } $Parameters = if ($Minimum) { @{ First = 1 } } else { @{ Last = 1 } } $Sorted | Select-Object @Parameters } else { $Type = if ($Maximum) { 'Maximum' } else { 'Minimum' } $Parameters = @{ Maximum = $Maximum Minimum = $Minimum } $Values | Measure-Object @Parameters | ForEach-Object { $_.$Type } } } } Invoke-GetExtremum $InputObject } End { Invoke-GetExtremum $Input } } function Get-Factorial { <# .SYNOPSIS Return factorial of Value, Value! .EXAMPLE Get-Factorial 10 # 3628800 .EXAMPLE 200 | factorial # 788657867364790503552363213932185062295135977687173263294742533244359449963403342920304284011984623904177212138919638830257642790242637105061926624952829931113462857270763317237396988943922445621451664240254033291864131227428294853277524242407573903240321257405579568660226031904170324062351700858796178922222789623703897374720000000000000000000000000000000000000000000000000 #> [CmdletBinding()] [OutputType([Int])] Param( [Parameter(Position = 0, ValueFromPipeline = $True)] [Int] $Value ) Process { if ($Value -eq 0) { 1 } else { 1..$Value | Invoke-Reduce { Param( [BigInt] $Acc, [BigInt] $Item ) [BigInt]::Multiply($Acc, $Item) } } } } function Get-LogisticSigmoid { <# .SYNOPSIS For a given value, x, returns value of logistic sigmoid function at x Note: Available as static method of Prelude class - [Prelude]::Sigmoid .DESCRIPTION The logistic sigmoid function is commonly used as an activation function within neural networks and to model population growth. .PARAMETER Midpoint Abscissa axis coordinate of logistic sigmoid function reflection point .PARAMETER MaximumValue Logistic sigmoid function maximum value .PARAMETER Derivative Switch parameter to determine which function to use, f(x) or f'(x) = f(x) * f(-x) #> [CmdletBinding()] [Alias('sigmoid')] [OutputType([Double])] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [Alias('x')] [Double] $Value, [Alias('k')] [Double] $GrowthRate = 1, [Alias('x0')] [Double] $Midpoint = 0, [Alias('L')] [Double] $MaximumValue = 1, [Switch] $Derivative ) Process { $Sigmoid = { Param($X) $MaximumValue / (1 + [Math]::Pow([Math]::E, (-1 * $GrowthRate) * ($X - $Midpoint))) } $Result = & $Sigmoid $Value if ($Derivative) { $Result * (1 - $Result) } else { $Result } } } function Get-Maximum { <# .SYNOPSIS Wrapper for Get-Extremum with the -Maximum switch #> [CmdletBinding()] [Alias('max')] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [Array] $Values ) Begin { if ($Values.Count -gt 0) { $Values | Get-Extremum -Maximum } } End { if ($Input.Count -gt 0) { $Input | Get-Extremum -Maximum } } } function Get-Mean { <# .SYNOPSIS Calculate mean (average) for list of numerical values .DESCRIPTION Specifically, this function returns the "expected value" (mean) for a discrete uniform random variable. .PARAMETER Trim Return "trimmed" mean where a certain number of items from the beginning and end of the sorted data are excluded from the mean. Note: If this parameter value is in the range (0,1), it will be treated as a percentage. .EXAMPLE 1..10 | mean .EXAMPLE 1..10 | mean -Trim 1 .EXAMPLE 1..10 | mean -Quadratic #> [CmdletBinding()] [Alias('mean')] [OutputType([System.Double])] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [Array] $Data, [Switch] $Arithmetic, [Switch] $Geometric, [Switch] $Harmonic, [Alias('RMS')] [Switch] $Quadratic, [ValidateRange(0, [Double]::PositiveInfinity)] [Double] $Trim = 0, [Array] $Weight ) End { if ($Input.Count -gt 0) { $Data = $Input } if ($Trim -gt 0 -and $Trim -lt 1) { $Trim = [Math]::Floor($Trim * $Data.Count) } $Type = Find-FirstTrueVariable 'Arithmetic', 'Geometric', 'Harmonic', 'Quadratic' $Data = $Data | Sort-Object $Data = $Data[$Trim..($Data.Count - 1 - $Trim)] switch ($Type) { 'Arithmetic' { ($Data | Get-Sum -Weight $Weight) / $Data.Count } 'Geometric' { $Product = $Data | Invoke-Reduce -Multiply [Double]([Math]::Pow($Product, 1 / $Data.Count)) } 'Harmonic' { if ($Weight) { '==> Harmonic mean does not use weights' | Write-Warning } $Sum = $Data | ForEach-Object { 1 / $_ } | Get-Sum $Data.Count / $Sum } 'Quadratic' { if ($Weight) { '==> Quadratic mean does not use weights' | Write-Warning } $Sum = $Data | ForEach-Object { [Math]::Pow($_, 2) } | Get-Sum [Math]::Sqrt($Sum / $Data.Count) } } } } function Get-Median { <# .SYNOPSIS Calculate median for list of numerical values .DESCRIPTION Specifically, this function returns the median for a discrete uniform random variable. .EXAMPLE 1..10 | median #> [CmdletBinding()] [Alias('median')] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [Array] $Data ) End { if ($Input.Count -gt 0) { $Data = $Input } $Sorted = $Data | Sort-Object $Index = $Sorted.Count / 2 if ($Sorted.Count % 2 -eq 0) { $Left = $Sorted[$Index - 1] $Right = $Sorted[$Index] } else { $Left = [Math]::Floor($Sorted[$Index]) $Right = $Left } ($Left + $Right) / 2 } } function Get-Minimum { <# .SYNOPSIS Wrapper for Get-Extremum with the -Minimum switch #> [CmdletBinding()] [Alias('min')] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [Array] $Values ) Begin { if ($Values.Count -gt 0) { $Values | Get-Extremum -Minimum } } End { if ($Input.Count -gt 0) { $Input | Get-Extremum -Minimum } } } function Get-Permutation { <# .SYNOPSIS Return permutaions of input object .DESCRIPTION Implements the "Steinhaus–Johnson–Trotter" algorithm that leverages adjacent transpositions ("swapping") combined with lexicographic ordering in order to create a list of permutations. In mathematical terms, the number of items return by Get-Permutation can be quantified as follows: Get-Permutation $n ==> (Get-Factorial $n) items = P(n,n) = n! Get-Permutation $n -Choose $k ==> ((Get-Factorial $n) / (Get-Factorial ($n - $k))) items = P(n,k) = n! / (n - k)! Get-Permutation $n -Choose $k -Unique ==> ((Get-Factorial $n) / ((Get-Factorial $k) * (Get-Factorial ($n - $k)))) items = C(n,k) = n! / k!(n - k)! Note: Get-Permutation will start to exhibit noticeable pause before completion for n = 7 .PARAMETER Words Combine individual permutations as strings (see examples) .PARAMETER Choose Return permutations selected from -Choose items. For a value of "k" for Choose parameter, the equivalent mathematical formula for the number items returned by "Get-Permutation n -Choose k" is: n! / (n - k)! .PARAMETER Unique Return only permutations that are unique up to set membership (order does not matter) .EXAMPLE 2 | Get-Permutation # @(0,1),@(1,0) 1,2 | Get-Permutation # @(1,2),@(2,1) 2 | Get-Permutation -Offset 1 # @(1,2).@(2,1) .EXAMPLE 'cat' | permute -Words # 'cat','cta','tca','tac','atc','act' .EXAMPLE 'hello' | permute -Choose 2 -Unique -Words # 'he','hl','hl','ho','el','el','eo','ll','lo','lo' #> [CmdletBinding()] [Alias('permute')] Param( [Parameter(Position = 0, ValueFromPipeline = $True)] [Array] $InputObject, [Parameter(Position = 1)] [Int] $Offset = 0, [Int] $Choose, [Switch] $Unique, [Switch] $Words ) Begin { function Invoke-Swap { <# .SYNOPSIS Swap two elements of an array .DESCRIPTION Uses the algorithm, b = (a += b -= a) - b #> Param( [Array] $Items, [Int] $Next, [Int] $Current ) $Items[$Next] = ($Items[$Current] += $Items[$Next] -= $Items[$Current]) - $Items[$Next] } function Test-Moveable { Param( [Parameter(Position = 0)] [Array] $Work, [Parameter(Position = 1)] [Array] $Direction, [Parameter(Position = 2)] [Int] $Index ) if (($Index -eq 0 -and $Direction[$Index] -eq 0) -or ($Index -eq ($Work.Count - 1) -and $Direction[$Index] -eq 1)) { return $False } if (($Index -gt 0) -and ($Direction[$Index] -eq 0) -and ($Work[$Index] -gt $Work[$Index - 1])) { return $True } if ($Index -lt ($Work.Count - 1) -and ($Direction[$Index] -eq 1) -and ($Work[$Index] -gt $Work[$Index + 1])) { return $True } if (($Index -gt 0) -and ($Index -lt $Work.Count)) { if (($Direction[$Index] -eq 0 -and $Work[$Index] -gt $Work[$Index - 1]) -or ($Direction[$Index] -eq 1 -and $Work[$Index] -gt $Work[$Index + 1])) { return $True } } return $False } function Test-MoveableExist { [OutputType([Bool])] Param( [Parameter(Position = 0)] [Array] $Work, [Parameter(Position = 1)] [Array] $Direction ) $IsMoveable = $False for ($Index = 0; $Index -lt $Work.Count; $Index++) { if (Test-Moveable -Work $Work -Direction $Direction -Index $Index) { $IsMoveable = $True Break } } $IsMoveable } function Find-LargestMoveable { Param( [Parameter(Position = 0)] [Array] $Work, [Parameter(Position = 1)] [Array] $Direction ) $Index = 0 foreach ($Item in $Work) { if ((Test-Moveable -Work $Work -Direction $Direction -Index $Index) -and ($Largest -lt $Item)) { $Largest = $Item $Position = $Index } $Index++ } $Position } function Invoke-Permutation { Param( [Parameter(Position = 0)] [Int] $Value, [Parameter(Position = 1)] [Int] $Offset, [Int] $Choose, [Switch] $Unique ) $Results = New-Object 'Object[]' @((Get-Factorial $Value)) $Work = 0..($Value - 1) | ForEach-Object { $_ + $Offset } $Direction = $Work | ForEach-Object { 0 } $Step = 1 $Results[0] = $Work.Clone() while ((Test-MoveableExist $Work $Direction)) { $Current = Find-LargestMoveable $Work $Direction $NextPosition = if ($Direction[$Current] -eq 0) { $Current - 1 } else { $Current + 1 } Invoke-Swap -Items $Work -Next $NextPosition -Current $Current Invoke-Swap -Items $Direction -Next $NextPosition -Current $Current 0..($Value - 1) | Where-Object { $Work[$_] -gt $Work[$NextPosition] } | ForEach-Object { $Direction[$_] = if ($Direction[$_] -eq 0) { 1 } else { 0 } } $Results[$Step] = $Work.Clone() $Step++ } if ($Choose -gt 0) { $Items = New-Object 'System.Collections.ArrayList' foreach ($Result in $Results) { [Void]$Items.Add($Result[0..($Choose - 1)]) } $Results = $Items | Select-Object -Unique } if ($Unique) { $Choices = New-Object 'System.Collections.ArrayList' foreach ($Result in $Results) { $Choice = $Result | Sort-Object [Void]$Choices.Add($Choice) } $Choices | Sort-Object -Unique } else { $Results } } $GetResults = { Param( [Parameter(Position = 0)] [Array] $InputObject ) $Count = $InputObject.Count $Items = $InputObject $Value = $Count if ($Count -gt 0) { if ($Count -eq 1) { $First = $InputObject[0] $Type = $First.GetType().Name if ($Null -ne $First -and $Type -eq 'String') { $Items = $First.ToCharArray() $Value = $Items.Count } elseif ($Type -match 'Int') { $Items = @() $Value = $First } } if ($Items.Count -gt 0) { $Result = [System.Collections.ArrayList]@{} $Parameters = @{ Value = $Value Offset = 0 Choose = $Choose Unique = $Unique } foreach ($Item in (Invoke-Permutation @Parameters)) { $Permutation = $Items[$Item] if ($Words) { $Permutation = $Permutation -join '' } [Void]$Result.Add($Permutation) } $Result } else { $Parameters = @{ Value = $Value Offset = $Offset Choose = $Choose Unique = $Unique } Invoke-Permutation @Parameters | Sort-Object -Property { $_ -join '' } } } } & $GetResults $InputObject } End { & $GetResults $Input } } function Get-Softmax { <# .SYNOPSIS Apply "softmax" function to list of numbers .EXAMPLE 1..10 | Get-Softmax .EXAMPLE Get-Softmax 1..10 #> [CmdletBinding()] [Alias('softmax')] [OutputType([Int[]])] [OutputType([Matrix])] Param( [Parameter(Position = 0, ValueFromPipeline = $True)] [Array] $Values ) Begin { function Get-Softmax_ { Param($Values) $Numerators = $Values | ForEach-Object { [Math]::Exp($_) } $Denominator = $Numerators | Get-Sum foreach ($Value in $Numerators) { $Value / $Denominator } } if ($Values.Count -gt 0) { if ($Values[0].GetType().Name -eq 'Matrix') { $Size = $Values.Size Get-Softmax_ -Values $Values.Values.Real | New-Matrix $Size } else { Get-Softmax_ -Values $Values } } } End { if ($Input.Count -gt 0) { if ($Input[0].GetType().Name -eq 'Matrix') { $Size = $Input.Size Get-Softmax_ -Values $Input.Values.Real | New-Matrix $Size } else { Get-Softmax_ -Values $Input } } } } function Get-Sum { <# .SYNOPSIS Calculate sum of list of numbers or count number of true values within a list .EXAMPLE 1..100 | sum # 5050 .EXAMPLE $True, $False, $False, $False, $True | Get-Sum # 2 #> [CmdletBinding()] [Alias('sum')] [OutputType([System.Numerics.Complex])] [OutputType([Int])] Param( [Parameter(Position = 0, ValueFromPipeline = $True)] [Array] $Values, [Parameter(Position = 1)] [Array] $Weight ) Begin { function Get-Sum_ { Param($Values) if ($Values.Count -gt 0) { switch ($Values[0].GetType().Name) { 'Boolean' { ($Values | Where-Object { $_ }).Count } Default { if ($Weight.Count -eq $Values.Count) { $Size = $Values.Count $X = $Values | New-Matrix -Size 1, $Size $W = $Weight | New-Matrix -Size $Size, $Size -Diagonal $Values = ($X * $W).Values } $Sum = 0 foreach ($Value in $Values) { $Sum += $Value } if ($Sum.Imaginary -eq 0) { $Sum.Real } else { $Sum } } } } } Get-Sum_ $Values } End { Get-Sum_ $Input } } function Get-Variance { <# .SYNOPSIS Return variance for discrete uniform random variable .DESCRIPTION The variance is basically the spread (dispersion) of the data. .PARAMETER Sample Divide by ($Data.Count - 1) instead of $Data.Count. Reasearch "degrees of freedom" for more information. May also be referred to as "unbiased". .EXAMPLE 1..10 | Get-Variance .EXAMPLE 1..10 | variance -Sample #> [CmdletBinding()] [Alias('variance')] Param( [Parameter(Position = 0, ValueFromPipeline = $True)] [Array] $Data, [Switch] $Sample ) End { $Values = if ($Input.Count -gt 0) { $Input } else { $Data } if ($Sample -and $Values.Count -gt 1) { $Mean = Get-Mean $Values $SquaredResidual = $Values | ForEach-Object { [Math]::Pow(($_ - $Mean), 2) } ($SquaredResidual | Get-Sum) / ($Values.Count - 1) } else { $Squared = $Values | ForEach-Object { [Math]::Pow($_, 2) } (Get-Mean $Squared) - [Math]::Pow((Get-Mean $Values), 2) } } } function Invoke-Imputation { <# .SYNOPSIS Impute missing values in a data set. .DESCRIPTION Performs "unit imputation" on set of values, replacing $Null or empty string values with a substitue value, -With. .PARAMETER With Value to replace null and empty values With .PARAMETER Limit Limit the number of values to be imputed .EXAMPLE 1, $Null, 3, $Null, 5 | Invoke-Imputation -With 42 # 1, 42, 3, 42, 5 .EXAMPLE 1, $Null, 3, $Null, 5 | Invoke-Imputation -With 42 -Limit 1 # 1, 42, 3, $Null, 5 #> [CmdletBinding()] [Alias('impute')] Param( [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline = $True)] [AllowNull()] [Array] $Values, [Parameter(Position = 1)] [Alias('Substitute')] $With = 0, [Int] $Limit ) Begin { function Invoke-Impute { Param( [Parameter(Position = 0)] [AllowNull()] [Array] $Values ) if ($Values.Count -gt 0) { $Result = @() $Count = 0 foreach ($Value in $Values) { $WithinLimit = (($Limit -gt 0) -and ($Count -lt $Limit)) -or ($Limit -eq 0) if ($WithinLimit -and [String]::IsNullOrEmpty($Value)) { $Result += $With $Count += 1 } else { $Result += $Value } } $Result } } Invoke-Impute $Values } End { Invoke-Impute $Input } } |