SCProject.Biz CODER logoOOP Statistical Functions




OOP Statistical Functions

This is an OOP example that focuses on Statistical Functions, with input via a NumericUpDown Control, and displaying output in a PropertyGrid Control. The OOP part is simple. There's a Class for each of these...


Each Class has just one familiar Public member - getValue, which returns either a single Decimal or an Array of Decimal. There is also a common Class which hosts methods used by more than one of the getValue Functions.

The common Class

This contains methods used in more than one of the OOP Core Classes


Public Class common

    Public Shared Function ocToArray(items As ListBox.ObjectCollection) As Decimal()
        Return items.Cast(Of Object).Select(Function(o) CDec(o.ToString)).OrderBy(Function(d) d).ToArray
    End Function

    Public Shared Function median(ByVal numbers() As Decimal) As Decimal
        Array.Sort(numbers)
        If numbers.Length = 0 Then
            Return 0
        ElseIf numbers.Length = 1 Then
            Return numbers(0)
        End If
        If numbers.Length Mod 2 = 1 Then 'odd count
            Return numbers(CInt(Math.Floor(numbers.Length / 2)))
        Else 'even count
            Dim lower As Integer = CInt(numbers.Length / 2 - 1)
            Dim higher As Integer = lower + 1
            Return (numbers(lower) + numbers(higher)) / 2
        End If
    End Function

End Class

The mean Class

Mean is just the average of an Array of numbers, which is easily calculated in .Net


Public Class mean
    Public Shared Function getValue(ByVal items As ListBox.ObjectCollection) As Decimal
        Dim numbers() As Decimal = common.ocToArray(items)
        Return numbers.Average
    End Function

End Class

The median Class

Median is the middle value in a sorted Array of numbers


Public Class median
    Public Shared Function getValue(ByVal items As ListBox.ObjectCollection) As Decimal
        Dim numbers() As Decimal = common.ocToArray(items)
        Return common.median(numbers)
    End Function

End Class

The mode Class

Mode is the number with the highest occurence in an Array of numbers. This can be multi-modal which means more than one number


Public Class mode
    Public Shared Function getValue(ByVal items As ListBox.ObjectCollection) As String
        Dim numbers() As Decimal = common.ocToArray(items)
        Dim map As New Dictionary(Of Decimal, Integer)

        For Each f As Decimal In numbers
            Dim c As Integer = 0
            For x As Integer = 0 To numbers.Length - 1
                If f = numbers(x) Then
                    c += 1
                End If
            Next x
            If Not map.ContainsKey(f) Then
                map.Add(f, c)
            Else
                map(f) = c
            End If
        Next

        Dim highest As Integer = map.Values.Max

        Dim counter As Integer = map.Where(Function(kvp) kvp.Value = highest).Count
        Dim modeValues As List(Of Decimal) = map.Where(Function(kvp) kvp.Value = highest).Select(Function(kvp) kvp.Key).Distinct.ToList

        If counter = items.Count Then
            Return "...(All)"
        Else
            Dim result As String = ""
            For Each f As Decimal In modeValues
                result &= f.ToString() & ", "
            Next f
            Return result.Substring(0, result.Length - 2)
        End If
    End Function

End Class

The range Class

Range is the highest number minus the lowest number in an Array of numbers. In this example, min, max, and range are returned.


Public Class range
    Public Shared Function getValue(ByVal items As ListBox.ObjectCollection) As Decimal()
        Dim numbers() As Decimal = common.ocToArray(items)
        Return New Decimal() {numbers.Min, numbers.Max, numbers.Max - numbers.Min}
    End Function

End Class

The iqr Class

IQR is inter-quartile range, which is calculated like this.

Q1 (first quartile) is the median of the first half of a sorted Array of numbers.
Q3 (third quartile) is the median of the second half of a sorted Array of numbers.
IQR is Q3 - Q1.

In this example, Q1, Q3, and IQR are returned.


Public Class iqr
    Public Shared Function getValue(ByVal items As ListBox.ObjectCollection) As Decimal()
        Dim numbers() As Decimal = common.ocToArray(items)
        Array.Sort(numbers)

        Dim i As Integer = CInt(Math.Floor(numbers.Count / 2))

        Dim firstHalf As New List(Of Decimal)(numbers.Take(i).ToArray)
        i = If(i = numbers.Count / 2, i, i + 1)
        Dim secondHalf As New List(Of Decimal)(numbers.Skip(i).Take(i).ToArray)

        Dim Q1 As Decimal = common.median(firstHalf.ToArray)
        Dim Q3 As Decimal = common.median(secondHalf.ToArray)

        Return New Decimal() {Q1, Q3, Q3 - Q1}
    End Function

End Class

The sd Class

Standard Deviation is used to quantify the amount of variation or dispersion of an Array of numbers. In this example, sample and population StdDev is calculated.


Public Class sd
    Public Shared Function getValue(ByVal items As ListBox.ObjectCollection) As Decimal()
        Dim numbers() As Decimal = common.ocToArray(items)

        Dim mean As Decimal = numbers.Average

        Dim squaredDifference(numbers.Length - 1) As Decimal
        For x As Integer = 0 To numbers.Length - 1
            squaredDifference(x) = CDec(Math.Pow(numbers(x) - mean, 2))
        Next x

        Return New Decimal() {CDec(Math.Sqrt(minusOneAverage(squaredDifference))), CDec(Math.Sqrt(squaredDifference.Average))}

    End Function

    Private Shared Function minusOneAverage(ByVal a() As Decimal) As Decimal
        If a.Length > 1 Then
            Return a.Sum / (a.Length - 1)
        Else
            Return Nothing
        End If
    End Function

End Class

The Coordinating Class

In this example a PropertyGrid Control is used for output. To use a PropertyGrid Control, you bind a Class to the Control, which shows the Properties contained in the class. This can be a read/write arrangement, but in this Class, the Properties are all ReadOnly.
This is a coordinating class for the OOP core as well as the Datasource for the PropertyGrid, as there is a Public method which, when invoked, gathers all of the Statistical Function values and assigns them to the ReadOnly Properties in the same Class.


Imports System.ComponentModel
<TypeConverter(GetType(PropertySorter))>
Public Class datasource

    Public Sub updateValues(listbox As ListBox)
        Dim b As Boolean = listbox.Items.Count > 0

        _mean = If(b, mean.getValue(listbox.Items).ToString, "")
        _median = If(b, median.getValue(listbox.Items).ToString, "")
        _mode = If(b, mode.getValue(listbox.Items).ToString, "")

        If b Then
            Dim values() As Decimal = range.getValue(listbox.Items)
            _min = values(0).ToString
            _max = values(1).ToString
            _range = values(2).ToString
            values = iqr.getValue(listbox.Items)
            _q1 = values(0).ToString
            _q3 = values(1).ToString
            _iqr = values(2).ToString
            values = sd.getValue(listbox.Items)
            _sample = values(0).ToString
            _population = values(1).ToString
        Else
            _min = ""
            _max = ""
            _range = ""
            _q1 = ""
            _q3 = ""
            _iqr = ""
            _sample = ""
            _population = ""
        End If

    End Sub

    Private _mean As String
    <SortedCategory("General -", 0, 4), DisplayName("Mean:"), PropertyOrder(0)>
    Public ReadOnly Property pmean As String
        Get
            Return _mean
        End Get
    End Property

    Private _median As String
    <SortedCategory("General -", 0, 4), DisplayName("Median:"), PropertyOrder(1)>
    Public ReadOnly Property pmedian As String
        Get
            Return _median
        End Get
    End Property

    Private _mode As String
    <SortedCategory("General -", 0, 4), DisplayName("Mode:"), PropertyOrder(2)>
    Public ReadOnly Property pmode As String
        Get
            Return _mode
        End Get
    End Property

    Private _min As String
    <SortedCategory("Range -", 1, 4), DisplayName("Min:"), PropertyOrder(3)>
    Public ReadOnly Property pmin As String
        Get
            Return _min
        End Get
    End Property

    Private _max As String
    <SortedCategory("Range -", 1, 4), DisplayName("Max:"), PropertyOrder(4)>
    Public ReadOnly Property pmax As String
        Get
            Return _max
        End Get
    End Property

    Private _range As String
    <SortedCategory("Range -", 1, 4), DisplayName("Range:"), PropertyOrder(5)>
    Public ReadOnly Property prange As String
        Get
            Return _range
        End Get
    End Property

    Private _q1 As String
    <SortedCategory("IQR -", 2, 4), DisplayName("Q1:"), PropertyOrder(6)>
    Public ReadOnly Property pq1 As String
        Get
            Return _q1
        End Get
    End Property

    Private _q3 As String
    <SortedCategory("IQR -", 2, 4), DisplayName("Q3:"), PropertyOrder(7)>
    Public ReadOnly Property pq3 As String
        Get
            Return _q3
        End Get
    End Property

    Private _iqr As String
    <SortedCategory("IQR -", 2, 4), DisplayName("IQR:"), PropertyOrder(8)>
    Public ReadOnly Property piqr As String
        Get
            Return _iqr
        End Get
    End Property

    Private _sample As String
    <SortedCategory("StdDev -", 3, 4), DisplayName("Sample:"), PropertyOrder(9)>
    Public ReadOnly Property psample As String
        Get
            Return _sample
        End Get
    End Property

    Private _population As String
    <SortedCategory("StdDev -", 3, 4), DisplayName("Population:"), PropertyOrder(10)>
    Public ReadOnly Property ppopulation As String
        Get
            Return _population
        End Get
    End Property

End Class

The custom Attributes

There are two custom Attributes...


The SortedCategoryAttribute

By default, the PropertyGrid sorts Categories alphabetically by name. The only way (in VB2017) to change that sort order is to trick the alphabetical sorter by prepending zero length unprintable characters to the Category display name. SubClassing the CategoryAttribute allows a custom CategoryAttribute to be used with three parameters instead of just the standard categoryKey. These parameters are: categoryKey (a string name), order (zero based) and categoryCount (which is the number of categories used in your class). The Protected GetLocalizedString Function prepends the correct number of zero length unprintable characters according to the input arguments you've provided, resulting in Categories displayed in the order you choose.


Imports System.ComponentModel

Public Class SortedCategoryAttribute
    Inherits CategoryAttribute

    Const trickString As String = Chr(31) & Chr(32)

    Private order As Integer
    Private categoryCount As Integer

    Public Sub New(ByVal categoryKey As String, order As Integer, categoryCount As Integer)
        MyBase.New(categoryKey)
        Me.order = order
        Me.categoryCount = categoryCount
    End Sub

    Protected Overrides Function GetLocalizedString(ByVal value As String) As String
        Dim x As Integer = Me.categoryCount - Me.order
        Dim s As String = ""
        While x >= 1
            s &= trickString
            x -= 1
        End While

        Return s & value
    End Function

End Class

The PropertyOrderAttribute

This is a simple Attribute holding an order value


<AttributeUsage(AttributeTargets.Property)>
Public Class PropertyOrderAttribute
    Inherits Attribute

    '
    ' Simple attribute for a property to allow an order to be specified
    '
    Public ReadOnly Property Order() As Integer

    Public Sub New(ByVal order As Integer)
        Me.Order = order
    End Sub

End Class

The TypeConverter

Including a PropertySorter means the properties within the categories will retain the order you specify, even when the sort alphabetically button is pressed, and also when returning to a categorised view. Examples of this type of sorter are freely and widely available on the internet.


Imports System.ComponentModel

Public Class PropertySorter
    Inherits ExpandableObjectConverter

    Public Overrides Function GetPropertiesSupported(ByVal context As ITypeDescriptorContext) As Boolean
        Return True
    End Function

    Public Overrides Function GetProperties(ByVal context As ITypeDescriptorContext, ByVal value As Object, ByVal attributes() As Attribute) As PropertyDescriptorCollection
        '
        ' This override returns a list of properties in order
        '
        Dim pdc As PropertyDescriptorCollection = TypeDescriptor.GetProperties(value, attributes)
        Dim dProperties As New Dictionary(Of String, Integer)
        For Each pd As PropertyDescriptor In pdc
            Dim attribute As PropertyOrderAttribute = TryCast(pd.Attributes(GetType(PropertyOrderAttribute)), PropertyOrderAttribute)
            dProperties.Add(pd.Name, If(Not attribute Is Nothing, attribute.Order, 0))
        Next pd
        '
        ' Perform the sorting using LINQ
        '
        Dim myList As List(Of KeyValuePair(Of String, Integer)) = dProperties.ToList()
        myList.Sort(Function(x As KeyValuePair(Of String, Integer), y As KeyValuePair(Of String, Integer))
                        If x.Value = y.Value Then
                            Return x.Key.CompareTo(y.Key)
                        Else
                            Return x.Value.CompareTo(y.Value)
                        End If
                        Return 0
                    End Function)
        '
        ' Pass in the ordered names for the PropertyDescriptorCollection to sort by
        '
        Return pdc.Sort(myList.Select(Function(kvp) kvp.Key).ToArray)

    End Function

End Class

Taking it further

OOP Programming in VB.Net makes easily readable code. Where possible, re-using methods is efficient. Making controls work as you want them to, results in usable programs, assuming your idea of how your program should function is realistic.
This example has a PropertyGrid that doesn't use the default sort order. This is achieved with the Custom Attributes and the TypeConverter, which work together with the datasource Class, and the PropertyGrid, to attain the desired sorting.

This example is available for download here...





Try it now...