Skip to content

Instantly share code, notes, and snippets.

@eyalk11
Last active October 13, 2025 12:25
Show Gist options
  • Select an option

  • Save eyalk11/7052231e839d218495f86488512584f6 to your computer and use it in GitHub Desktop.

Select an option

Save eyalk11/7052231e839d218495f86488512584f6 to your computer and use it in GitHub Desktop.
Powershell function to get histogram of data
#based on https://www.powershellgallery.com/packages/Statistics/1.1.53/Content/Get-Histogram.ps1
#improved. by Eyal Karni
function Get-Histogram {
[CmdletBinding(DefaultParameterSetName='BucketCount')]
Param(
[Parameter(Mandatory, ValueFromPipeline, Position=1)]
[ValidateNotNullOrEmpty()]
[array]
$InputObject
,
[Parameter(Position=2)]
[ValidateNotNullOrEmpty()]
[string]
$Property
,
[Parameter()]
[ValidateNotNullOrEmpty()]
[float]
$Minimum
,
[Parameter()]
[ValidateNotNullOrEmpty()]
[float]
$Maximum
,
[Parameter()]
[ValidateNotNullOrEmpty()]
[Alias('Width')]
[float]
$BucketWidth = 1
,
[Parameter()]
[ValidateNotNullOrEmpty()]
[Alias('Count')]
[float]
$BucketCount
,
[Parameter()]
[switch]
$Visualize
,
[Parameter()]
[ValidateRange(1, 200)]
[int]
$BarWidth = 73
,
[Parameter()]
[switch]
$Weighted
)
Begin {
Write-Verbose ('[{0}] Initializing' -f $MyInvocation.MyCommand)
$Buckets = @{}
$Data = @()
}
Process {
Write-Verbose ('[{0}] Processing {1} items' -f $MyInvocation.MyCommand, $InputObject.Length)
$InputObject | ForEach-Object {
if ($Weighted) {
# Expect data in format (probability, value) or [probability, value]
if ($_ -is [array] -and $_.Count -eq 2) {
$Data += [PSCustomObject]@{
Weight = $_[0]
Value = $_[1]
}
} elseif ($_.GetType().ToString() -like 'System.Tuple*') {
$Data += [PSCustomObject]@{
Weight = $_.Item1
Value = $_.Item2
}
} else {
Write-Host $_
throw ('Weighted data must be in format (probability, value) or [probability, value]')
}
} else {
if ($Property) {
if (-Not ($_ | Select-Object -ExpandProperty $Property -ErrorAction SilentlyContinue)) {
throw ('Input object does not contain a property called <{0}>.' -f $Property)
}
}
$Data += $_
}
}
}
End {
Write-Verbose ('[{0}] Building histogram' -f $MyInvocation.MyCommand)
Write-Debug ('[{0}] Retrieving measurements from upstream cmdlet.' -f $MyInvocation.MyCommand)
if ($Weighted) {
$Stats = $Data | Microsoft.PowerShell.Utility\Measure-Object -Minimum -Maximum -Property Value
} elseif ($Property) {
$Stats = $Data | Microsoft.PowerShell.Utility\Measure-Object -Minimum -Maximum -Property $Property
} else {
$Stats = $Data | Microsoft.PowerShell.Utility\Measure-Object -Minimum -Maximum
}
if (-Not $PSBoundParameters.ContainsKey('Minimum')) {
$Minimum = $Stats.Minimum
Write-Debug ('[{0}] Minimum value not specified. Using smallest value ({1}) from input data.' -f $MyInvocation.MyCommand, $Minimum)
}
if (-Not $PSBoundParameters.ContainsKey('Maximum')) {
$Maximum = $Stats.Maximum
Write-Debug ('[{0}] Maximum value not specified. Using largest value ({1}) from input data.' -f $MyInvocation.MyCommand, $Maximum)
}
if (-Not $PSBoundParameters.ContainsKey('BucketCount')) {
$BucketCount = [math]::Ceiling(($Maximum - $Minimum) / $BucketWidth)
Write-Debug ('[{0}] Bucket count not specified. Calculated {1} buckets from width of {2}.' -f $MyInvocation.MyCommand, $BucketCount, $BucketWidth)
}
if ($BucketCount -gt 100) {
Write-Warning ('[{0}] Generating {1} buckets' -f $MyInvocation.MyCommand, $BucketCount)
}
Write-Debug ('[{0}] Building buckets using: Minimum=<{1}> Maximum=<{2}> BucketWidth=<{3}> BucketCount=<{4}>' -f $MyInvocation.MyCommand, $Minimum, $Maximum, $BucketWidth, $BucketCount)
$OverallCount = 0
$Buckets = 1..$BucketCount | ForEach-Object {
[pscustomobject]@{
Index = $_
lowerBound = $Minimum + ($_ - 1) * $BucketWidth
upperBound = $Minimum + $_ * $BucketWidth
Count = 0
RelativeCount = 0
Group = @()
PSTypeName = 'HistogramBucket'
}
}
Write-Debug ('[{0}] Building histogram' -f $MyInvocation.MyCommand)
$Data | ForEach-Object {
if ($Weighted) {
$Value = $_.Value
$Weight = $_.Weight
} elseif ($Property) {
$Value = $_.$Property
$Weight = 1
} else {
$Value = $_
$Weight = 1
}
if ($Value -ge $Minimum -and $Value -le $Maximum) {
$BucketIndex = [math]::Floor(($Value - $Minimum) / $BucketWidth)
if ($BucketIndex -lt $Buckets.Length) {
$Buckets[$BucketIndex].Count += $Weight
$Buckets[$BucketIndex].Group += $_
$OverallCount += $Weight
}
}
}
Write-Debug ('[{0}] Adding relative count' -f $MyInvocation.MyCommand)
$Buckets | ForEach-Object {
if ($OverallCount -gt 0) {
$_.RelativeCount = $_.Count / $OverallCount
} else {
$_.RelativeCount = 0
}
}
if ($Visualize) {
Write-Debug ('[{0}] Generating visualization' -f $MyInvocation.MyCommand)
$MaxCount = ($Buckets | Measure-Object -Property Count -Maximum).Maximum
$Buckets | Where-Object { $_.Count -gt 0 } | ForEach-Object {
# Format the bucket range/label
$Label = if ($Property) {
"[{0:N1}-{1:N1}]" -f $_.lowerBound, $_.upperBound
} else {
"[{0:N1}-{1:N1}]" -f $_.lowerBound, $_.upperBound
}
# Calculate percentage
$Percentage = if ($OverallCount -gt 0) {
[int](100 * $_.Count / $OverallCount)
} else {
0
}
# Calculate bar length based on proportion to max count
$BarLength = if ($MaxCount -gt 0) {
[int]($BarWidth * $_.Count / $MaxCount)
} else {
0
}
# Create the bar
$Bar = ("*" * $BarLength).PadRight($BarWidth)
# Format and display the line
$Line = "{0} {1}% {2} [{3}]" -f `
$Label.PadLeft(20), `
$Percentage.ToString().PadLeft(3), `
$Bar, `
$_.Count
Write-Host $Line -ForegroundColor Green
}
}
Write-Debug ('[{0}] Returning histogram' -f $MyInvocation.MyCommand)
$Buckets
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment