Skip to content

Added pipeline functionality and made minor fixes #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@
| :------: | :-------: | :------: |
| [![Build status](https://ci.appveyor.com/api/projects/status/e56ra8c3q1jtc19o?svg=true)](https://ci.appveyor.com/project/nicholasdille/powershell-statistics) | [![Coverage Status](https://coveralls.io/repos/github/nicholasdille/PowerShell-Statistics/badge.svg?branch=master)](https://coveralls.io/github/nicholasdille/PowerShell-Statistics?branch=master) | [![Download](https://img.shields.io/badge/powershellgallery-Statistics-blue.svg)](https://www.powershellgallery.com/packages/Statistics/)

### Changes as compared to original repository
Get-Histogram
- Added DefaultDisplayPropertySet (Index,Count,lowerBound,upperBound)
- Made function take input from pipeline (not processing by pipeline but just as syntactic sugar)
- Replaced arraylist "pipeline data collector" by automatic $Input variable
- Changed gh alias to ghist (collided with Get-Help for me)
- Cannot get the test "Honors minimum and maximum values" working (mock call to write-warning)

Add-Bar
- Fixed double selection of Count property
- Changed name of InputObject to Data for consistency
- Replaced arraylist "pipeline data collector" by automatic $Input variable

Measure-Object
- Made function take input from pipeline (not processing by pipeline but just as syntactic sugar)

Show-Measurement
- Made function take input from pipeline (not processing by pipeline but just as syntactic sugar)
- Changed name of InputObject to Data for consistency
- Could not get the 'Produces output' test working (mock call to write-host)
- Decreased width to account for text in front of graph

# Introduction

Statistical analysis
Expand Down Expand Up @@ -229,4 +251,4 @@ Timestamp Value DayOfWeek Year Month Hour WeekOfYear

# Credits

Build scripts based on [PSDeploy](https://github.com/RamblingCookieMonster/PSDeploy) by [Warren Frame](http://ramblingcookiemonster.github.io/)
Build scripts based on [PSDeploy](https://github.com/RamblingCookieMonster/PSDeploy) by [Warren Frame](http://ramblingcookiemonster.github.io/)
45 changes: 14 additions & 31 deletions Statistics/Add-Bar.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[array]
$InputObject
$Data
,
[Parameter()]
[ValidateNotNullOrEmpty()]
Expand All @@ -17,46 +17,29 @@
$Width = $( if ($Host.UI.RawUI.MaxWindowSize.Width) { $Host.UI.RawUI.MaxWindowSize.Width - 30 } else { 50 } )
)

Begin {
Write-Verbose ('[{0}] Initializing' -f $MyInvocation.MyCommand)

$ValueFromParameter = $PSBoundParameters.ContainsKey('InputObject')
$Data = New-Object -TypeName System.Collections.ArrayList
}

Process {
if ($ValueFromParameter) {
$Data = $InputObject
foreach ($_ in $Data) {
if (-Not (Get-Member -InputObject $_ -MemberType Properties -Name $Property)) {
throw ('Input object does not contain a property called <{0}>.' -f $Property)
}
}

} else {
Write-Verbose ('[{0}] Processing {1} items' -f $MyInvocation.MyCommand, $InputObject.Length)

$InputObject | ForEach-Object {
if (-Not (Get-Member -InputObject $_ -MemberType Properties -Name $Property)) {
throw ('Input object does not contain a property called <{0}>.' -f $Property)
}
[void]$Data.Add($_)
End {
# If the argument for the Data parameter was not provided via pipeline set the input to the provided argument
# otherwise use the automatic Input variable
if (!$PSCmdlet.MyInvocation.ExpectingInput) {
$Input = $Data
}
foreach ($_ in $Input) {
if (-Not (Get-Member -InputObject $_ -MemberType Properties -Name $Property)) {
throw ('Input object does not contain a property called <{0}>.' -f $Property)
}
}
}

End {

Write-Verbose ('[{0}] Adding bars for width {1}' -f $MyInvocation.MyCommand, $Width)

$Count = $Data | Microsoft.PowerShell.Utility\Measure-Object -Maximum -Property $Property | Select-Object -ExpandProperty Maximum
$Count = $Input| Microsoft.PowerShell.Utility\Measure-Object -Maximum -Property $Property | Select-Object -ExpandProperty Maximum
Write-Debug ('[{0}] Maximum value is {1}. This value will be {2} characters long.' -f $MyInvocation.MyCommand, $Count, $Width)

$Bars = foreach ($_ in $Data) {
$Bars = foreach ($_ in $Input) {
$RelativeCount = [math]::Round($_.$Property / $Count * $Width, 0)
Write-Debug ('[{0}] Value of {1} will be displayed using {2} characters.' -f $MyInvocation.MyCommand, $_.Property, $RelativeCount)

Write-Debug ('[{0}] Adding member to input object.' -f $MyInvocation.MyCommand)
$Item = $_ | Select-Object -Property Index,Count,$Property | Add-Member -MemberType NoteProperty -Name Bar -Value ('#' * $RelativeCount) -PassThru
$Item = $_ | Select-Object -Property Index, $Property | Add-Member -MemberType NoteProperty -Name Bar -Value ('#' * $RelativeCount) -PassThru

Write-Debug ('[{0}] Adding type name to output object.' -f $MyInvocation.MyCommand)
$Item.PSTypeNames.Insert(0, 'HistogramBar')
Expand Down
38 changes: 26 additions & 12 deletions Statistics/Get-Histogram.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function Get-Histogram {
[CmdletBinding(DefaultParameterSetName='BucketCount')]
[CmdletBinding(DefaultParameterSetName = 'BucketCount')]
Param(
[Parameter(Mandatory)]
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[array]
$Data
Expand Down Expand Up @@ -33,13 +33,22 @@
[float]
$BucketCount
)

Process {
Begin {
# Define default display properties
$ddps = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet', [string[]]('Index', 'Count', 'lowerBound', 'upperBound'))
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]$ddps
}
End {
Write-Verbose ('[{0}] Building histogram' -f $MyInvocation.MyCommand)

Write-Debug ('[{0}] Retrieving measurements from upstream cmdlet for {1} values' -f $MyInvocation.MyCommand, $Data.Count)
# If the argument for the Data parameter was not provided via pipeline set the input to the provided argument
# otherwise use the automatic Input variable
if (!$PSCmdlet.MyInvocation.ExpectingInput) {
$Input = $Data
}
Write-Debug ('[{0}] Retrieving measurements from upstream cmdlet for {1} values' -f $MyInvocation.MyCommand, $Input.Count)
Write-Progress -Activity 'Measuring data'
$Stats = $Data | Microsoft.PowerShell.Utility\Measure-Object -Minimum -Maximum -Property $Property

$Stats = $Input | Microsoft.PowerShell.Utility\Measure-Object -Minimum -Maximum -Property $Property

if (-Not $PSBoundParameters.ContainsKey('Minimum')) {
$Minimum = $Stats.Minimum
Expand All @@ -64,7 +73,7 @@
[pscustomobject]@{
Index = $_
lowerBound = $Minimum + ($_ - 1) * $BucketWidth
upperBound = $Minimum + $_ * $BucketWidth
upperBound = $Minimum + $_ * $BucketWidth
Count = 0
RelativeCount = 0
Group = New-Object -TypeName System.Collections.ArrayList
Expand All @@ -74,10 +83,10 @@

Write-Debug ('[{0}] Building histogram' -f $MyInvocation.MyCommand)
$DataIndex = 1
foreach ($_ in $Data) {
foreach ($_ in $Input) {
$Value = $_.$Property

Write-Progress -Activity 'Filling buckets' -PercentComplete ($DataIndex / $Data.Count * 100)
Write-Progress -Activity 'Filling buckets' -PercentComplete ($DataIndex / $Input.Count * 100)

if ($Value -ge $Minimum -and $Value -le $Maximum) {
$BucketIndex = [math]::Floor(($Value - $Minimum) / $BucketWidth)
Expand All @@ -91,14 +100,19 @@
++$DataIndex
}

Write-Debug ('[{0}] Adding relative count' -f $MyInvocation.MyCommand)
Write-Debug ('[{0}] Adding relative count and default properties' -f $MyInvocation.MyCommand)
foreach ($_ in $Buckets) {
# Add an type name to make the default properties work
[void]$_.PSObject.TypeNames.Insert(0, 'Statistics.Buckets')
# Attach default display property set
Add-Member -InputObject $_ -MemberType MemberSet -Name PSStandardMembers -Value $PSStandardMembers
$_.RelativeCount = if ($OverallCount -gt 0) { $_.Count / $OverallCount } else { 0 }
}

Write-Debug ('[{0}] Returning histogram' -f $MyInvocation.MyCommand)
$Buckets

}
}

New-Alias -Name gh -Value Get-Histogram -Force
New-Alias -Name ghist -Value Get-Histogram -Force
48 changes: 27 additions & 21 deletions Statistics/Measure-Object.ps1
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
function Measure-Object {
[CmdletBinding()]
Param(
[Parameter(Mandatory)]
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[array]
$Data
Expand All @@ -12,45 +12,51 @@
$Property
)

Process {
End {
#region Percentiles require sorted data
$Data = $Data | Sort-Object -Property $Property
# If the argument for the Data parameter was not provided via pipeline set the input to the provided argument
# otherwise use the automatic Input variable
if (!$PSCmdlet.MyInvocation.ExpectingInput) {
$Input = $Data
}
$Input = $Input | Sort-Object -Property $Property
#endregion

#region Grab basic measurements from upstream Measure-Object
$Stats = $Data | Microsoft.PowerShell.Utility\Measure-Object -Property $Property -Minimum -Maximum -Sum -Average
$Stats = $Input | Microsoft.PowerShell.Utility\Measure-Object -Property $Property -Minimum -Maximum -Sum -Average
#endregion

#region Calculate median
Write-Debug ('[{0}] Number of data items is <{1}>' -f $MyInvocation.MyCommand.Name, $Data.Count)
if ($Data.Count % 2 -eq 0) {
Write-Debug ('[{0}] Number of data items is <{1}>' -f $MyInvocation.MyCommand.Name, $Input.Count)
if ($Input.Count % 2 -eq 0) {
Write-Debug ('[{0}] Even number of data items' -f $MyInvocation.MyCommand.Name)

$MedianIndex = ($Data.Count / 2) - 1
$MedianIndex = ($Input.Count / 2) - 1
Write-Debug ('[{0}] Index of Median is <{1}>' -f $MyInvocation.MyCommand.Name, $MedianIndex)

$LowerMedian = $Data[$MedianIndex] | Select-Object -ExpandProperty $Property
$UpperMedian = $Data[$MedianIndex + 1] | Select-Object -ExpandProperty $Property
$LowerMedian = $Input[$MedianIndex] | Select-Object -ExpandProperty $Property
$UpperMedian = $Input[$MedianIndex + 1] | Select-Object -ExpandProperty $Property
Write-Debug ('[{0}] Lower Median is <{1}> and upper Median is <{2}>' -f $MyInvocation.MyCommand.Name, $LowerMedian, $UpperMedian)

$Median = ([double]$LowerMedian + [double]$UpperMedian) / 2
Write-Debug ('[{0}] Average of lower and upper Median is <{1}>' -f $MyInvocation.MyCommand.Name, $Median)

} else {
}
else {
Write-Debug ('[{0}] Odd number of data items' -f $MyInvocation.MyCommand.Name)

$MedianIndex = [math]::Ceiling(($Data.Count - 1) / 2)
$MedianIndex = [math]::Ceiling(($Input.Count - 1) / 2)
Write-Debug ('[{0}] Index of Median is <{1}>' -f $MyInvocation.MyCommand.Name, $MedianIndex)

$Median = $Data[$MedianIndex] | Select-Object -ExpandProperty $Property
$Median = $Input[$MedianIndex] | Select-Object -ExpandProperty $Property
Write-Debug ('[{0}] Median is <{1}>' -f $MyInvocation.MyCommand.Name, $Median)
}
Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Median' -Value $Median
#endregion

#region Calculate variance
$Variance = 0
foreach ($_ in $Data) {
foreach ($_ in $Input) {
$Variance += [math]::Pow($_.$Property - $Stats.Average, 2) / $Stats.Count
}
$Variance /= $Stats.Count
Expand All @@ -63,14 +69,14 @@
#endregion

#region Calculate percentiles
$Percentile10Index = [math]::Ceiling(10 / 100 * $Data.Count)
$Percentile25Index = [math]::Ceiling(25 / 100 * $Data.Count)
$Percentile75Index = [math]::Ceiling(75 / 100 * $Data.Count)
$Percentile90Index = [math]::Ceiling(90 / 100 * $Data.Count)
Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile10' -Value $Data[$Percentile10Index].$Property
Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile25' -Value $Data[$Percentile25Index].$Property
Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile75' -Value $Data[$Percentile75Index].$Property
Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile90' -Value $Data[$Percentile90Index].$Property
$Percentile10Index = [math]::Ceiling(10 / 100 * $Input.Count)
$Percentile25Index = [math]::Ceiling(25 / 100 * $Input.Count)
$Percentile75Index = [math]::Ceiling(75 / 100 * $Input.Count)
$Percentile90Index = [math]::Ceiling(90 / 100 * $Input.Count)
Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile10' -Value $Input[$Percentile10Index].$Property
Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile25' -Value $Input[$Percentile25Index].$Property
Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile75' -Value $Input[$Percentile75Index].$Property
Add-Member -InputObject $Stats -MemberType NoteProperty -Name 'Percentile90' -Value $Input[$Percentile90Index].$Property
#endregion

#region Calculate Tukey's range for outliers
Expand Down
46 changes: 23 additions & 23 deletions Statistics/Show-Measurement.ps1
Original file line number Diff line number Diff line change
@@ -1,54 +1,54 @@
function Show-Measurement {
[CmdletBinding()]
Param(
[Parameter(Mandatory)]
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateNotNullOrEmpty()]
[object]
$InputObject
$Data
,
[Parameter()]
[ValidateNotNullOrEmpty()]
[int]
$Width = $( if ($Host.UI.RawUI.MaxWindowSize.Width) { $Host.UI.RawUI.MaxWindowSize.Width - 10 } else { 90 } )
$Width = $( if ($Host.UI.RawUI.MaxWindowSize.Width) { $Host.UI.RawUI.MaxWindowSize.Width - 25 } else { 90 } )
,
[switch]
$PassThru
)

Process {
End {
#region Generate visualization of measurements
$AvgSubDevIndex = [math]::Round(($InputObject.Average - $InputObject.StandardDeviation) / $InputObject.Maximum * $Width, 0)
$AvgIndex = [math]::Round( $InputObject.Average / $InputObject.Maximum * $Width, 0)
$AvgAddDevIndex = [math]::Round(($InputObject.Average + $InputObject.StandardDeviation) / $InputObject.Maximum * $Width, 0)
$AvgSubConfIndex = [math]::Round(($InputObject.Average - $InputObject.Confidence95) / $InputObject.Maximum * $Width, 0)
$AvgAddConfIndex = [math]::Round(($InputObject.Average + $InputObject.Confidence95) / $InputObject.Maximum * $Width, 0)
$MedIndex = [math]::Round( $InputObject.Median / $InputObject.Maximum * $Width, 0)
$P10Index = [math]::Round( $InputObject.Percentile10 / $InputObject.Maximum * $Width, 0)
$P25Index = [math]::Round( $InputObject.Percentile25 / $InputObject.Maximum * $Width, 0)
$P75Index = [math]::Round( $InputObject.Percentile75 / $InputObject.Maximum * $Width, 0)
$P90Index = [math]::Round( $InputObject.Percentile90 / $InputObject.Maximum * $Width, 0)
$P25SubTukIndex = [math]::Round(($InputObject.Percentile25 - $InputObject.TukeysRange) / $InputObject.Maximum * $Width, 0)
$P75AddTukIndex = [math]::Round(($InputObject.Percentile75 + $InputObject.TukeysRange) / $InputObject.Maximum * $Width, 0)
$AvgSubDevIndex = [math]::Round(($Data.Average - $Data.StandardDeviation) / $Data.Maximum * $Width, 0)
$AvgIndex = [math]::Round( $Data.Average / $Data.Maximum * $Width, 0)
$AvgAddDevIndex = [math]::Round(($Data.Average + $Data.StandardDeviation) / $Data.Maximum * $Width, 0)
$AvgSubConfIndex = [math]::Round(($Data.Average - $Data.Confidence95) / $Data.Maximum * $Width, 0)
$AvgAddConfIndex = [math]::Round(($Data.Average + $Data.Confidence95) / $Data.Maximum * $Width, 0)
$MedIndex = [math]::Round( $Data.Median / $Data.Maximum * $Width, 0)
$P10Index = [math]::Round( $Data.Percentile10 / $Data.Maximum * $Width, 0)
$P25Index = [math]::Round( $Data.Percentile25 / $Data.Maximum * $Width, 0)
$P75Index = [math]::Round( $Data.Percentile75 / $Data.Maximum * $Width, 0)
$P90Index = [math]::Round( $Data.Percentile90 / $Data.Maximum * $Width, 0)
$P25SubTukIndex = [math]::Round(($Data.Percentile25 - $Data.TukeysRange) / $Data.Maximum * $Width, 0)
$P75AddTukIndex = [math]::Round(($Data.Percentile75 + $Data.TukeysRange) / $Data.Maximum * $Width, 0)

Write-Debug "P10=$P10Index P25=$P25Index A=$AvgIndex M=$MedIndex sA=$AvgSubDevIndex As=$AvgAddDevIndex cA=$AvgSubConfIndex aC=$AvgAddConfIndex o=$P25SubTukIndex O=$P75AddTukIndex P75=$P75Index P90=$P90Index"

$graph = @()
$graph += 'Range : ' + '---------|' * ($Width / 10)
$graph += '10% Percentile : ' + ' ' * $P10Index + 'P10'
$graph += '25% Percentile : ' + ' ' * $P25Index + 'P25'
$graph += 'Average : ' + ' ' * $AvgIndex + 'A'
$graph += '10% Percentile : ' + ' ' * $P10Index + 'P10'
$graph += '25% Percentile : ' + ' ' * $P25Index + 'P25'
$graph += 'Average : ' + ' ' * $AvgIndex + 'A'
$graph += 'Standard Deviation: ' + (New-RangeString -Width $Width -LeftIndex $AvgSubDevIndex -RightIndex $AvgAddDevIndex -LeftIndicator 's' -RightIndicator 'S')
$graph += '95% Confidence : ' + (New-RangeString -Width $Width -LeftIndex $AvgSubConfIndex -RightIndex $AvgAddConfIndex -LeftIndicator 'c' -RightIndicator 'C')
$graph += 'Tukeys Range : ' + (New-RangeString -Width $Width -LeftIndex $P25SubTukIndex -RightIndex $P75AddTukIndex -LeftIndicator 'o' -RightIndicator 'O')
$graph += 'Median : ' + ' ' * $MedIndex + 'M'
$graph += '75% Percentile : ' + ' ' * $P75Index + 'P75'
$graph += '90% Percentile : ' + ' ' * $P90Index + 'P90'
$graph += 'Median : ' + ' ' * $MedIndex + 'M'
$graph += '75% Percentile : ' + ' ' * $P75Index + 'P75'
$graph += '90% Percentile : ' + ' ' * $P90Index + 'P90'
$graph += 'Range : ' + '---------|' * ($Width / 10)
#endregion

#region Return graph
if ($PassThru) {
$InputObject
$Data
}
Write-Host ($graph -join "`n")
#endregion
Expand Down
Loading