# 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...

• mean
• median
• mode
• range
• iqr
• stddev

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
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...

• SortedCategory
• PropertyOrder

### 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.