vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#

https://www.vbarchiv.net
Rubrik: Variablen/Strings · Algorithmen/Mathematik   |   VB-Versionen: VB200820.02.09
Berechnung von Perzentilwerten einer Datenreihe

Erweiterungsmethode für numerische System.Arrays zur Bestimmung der Perzentil-Werte einer Datenreihe.

Autor:   Manfred BohnBewertung:  Views:  13.892 
ohne HomepageSystem:  Win2k, WinXP, Win7, Win8, Win10, Win11kein Beispielprojekt 

Die Definition der Perzentile entnimmt man einem Statistik-Lehrbuch:

Das P-te Perzentil einer aufsteigend geordneten Folge von N Meßwerten ist der Wert mit der Rangnummer P*(N+1)/100 oder - wenn diese Rangnummer nicht ganzzahlig ist, der (linear) interpolierte Wert zwischen den in der Rangreihe benachbarten Werten, unterhalb dem P % aller Meßwerte liegen.

Der Wert des Perzentils 75 (=P75) gibt an, dass in der ausgewerteten Datenreihe 25% der Werte größer oder gleich diesem Wert sind.

Perzentil-Werte werden z.B. verwendet, um robuste (d.h. von einzelnen Ausreißer-Werten in den Daten wenig beeinflußbare) statistische Kennwerte zu bilden.

Einige Details zum Code:
Die generische Array-Erweiterungsmethode 'Percentil_Val' ermittelt zu einer Datenreihe (Parameter: Daten) den geforderten Perzentil-Wert (Parameter: Percentil). Der zu beachtende Streubereich der Datenausprägungen kann optional eingeschränkt werden (Parameter: MinValue, MaxValue).

Die Methode verarbeitet Arrays, deren Elemente 'Primitives' sind, sowie den präzisen Gleitkomma-Typ 'Decimal'. Bei IEEE-Daten (Single, Double) werden Sonderwerte gefiltert. Es müssen mindestens 10 plausible Daten vorliegen. Die Funktion gibt den Perzentil-Wert im Datentyp des Parameter-Arrays zurück.

Die Routine löst ggf. Ausnahmen aus und sollte innerhalb von Try/Catch-Blöcken verwendet werden.

Das Filtern und Sortieren der Daten erfolgt durch eine LINQ-Abfrage unter Verwendung der 'ToArray'-Erweiterung, die die erzeugte 'In-Memory-Query' in ein Array überträgt (Hilfsfunktion: 'FilterAndSort').

Einige Details zu VB:
Soweit Berechnungen (Interpolation) notwendig sind, werden sie unter Verwendung des Datentyps Double durchgeführt. Die erforderlichen Konvertierungen erledigen die HilfsRoutinen 'GetDouble' (Function) und 'GetEntry' (SUB) - unter Verwendung der ChangeType-Methode der System.Convert-Klasse. Beim nicht-primitiven Datentyp 'Decimal' ist das evt. mit einem (vernachlässigbaren) Genauigkeitsverlust der Interpolation verbunden.

Um bei den optionalen Double-Parametern erkennen zu können, ob der Benutzer eine Vorgabe gemacht hat, ist die Voreinstellung 'NaN' verwendet worden. Die Voreinstellung 'Nothing' übergibt nämlich eine 0. Null-fähige Parameter (z.B. Double?) können nicht optional sein.

Man beachte, dass die Math-Routinen 'Floor' und 'Ceiling' zwar Ganzzahlen bestimmen, aber diesen Wert dann als 'Double' zurückgeben.

Man beachte auch, dass bei der Array-Übergabe 'ByVal' zwar eine lokale Array-Variable gebildet wird, die aber auf die Elemente (stets Referenzen!) des externen Array zeigt. Auch Werttypen müssen deshalb bei Bedarf explizit lokal gemacht werden (hier: durch die LINQ-Abfrage).

Anwendungsbeispiele:

Public Sub Percentil_Demo()
 
  ' Integer-Array
  Dim int_arr(777) As Integer
  int_arr.Fill_Array()
  ' 95er Perzentil
  Dim Pz95 As Integer = int_arr.Percentil_Val(95)
 
  ' Single-Array mit einigen Sonderwerten
  Dim sng_arr(349) As Single
  sng_arr.Fill_Array()
  sng_arr(90) = Single.NaN
  sng_arr(100) = Single.NegativeInfinity
  sng_arr(55) = Single.PositiveInfinity
  sng_arr(6) = Single.MaxValue
 
  ' nur Werte zwischen -5000 und 0 werden einbezogen
  Dim Perz50 As Single = sng_arr.Percentil_Val(50, -5000, 0)
 
 
  ' Decimal-Array erstellen und füllen
  Dim decarr(2590) As Decimal
  decarr.Fill_Array()
 
  ' 2 Dezile
  Dim P10 As Decimal = decarr.Percentil_Val(10)
  Dim P90 As Decimal = decarr.Percentil_Val(90)
 
  ' Quartile
  Dim Q1 As Decimal = decarr.Percentil_Val(25)
  Dim Q2 As Decimal = decarr.Percentil_Val(50)
  Dim Q3 As Decimal = decarr.Percentil_Val(75)
 
  ' Aus den Perzentilen abgeleitete Kennwerte:
 
  ' Zentrale Tendenz der Daten
  Dim Median As Decimal = Q2
  ' bei Berechnung des getrimmten Mittelwertes werden nur 
  ' die mittleren Daten der sortierten Meßreihe verwendet
  Dim Getrimmter_Mittelwert As Decimal = _
    decarr.FilterAndSort(P10, P90).Average ' Linq-Erweiterung
 
  ' Streuungsbereich der Daten
  Dim Quartilabstand As Decimal = Q3 - Q2
  Dim DezilDifferenz As Decimal = P90 - P10
  ' zum Vergleich:
  Dim Spannweite As Decimal = decarr.Max - decarr.Min
 
  ' Schiefe der Datenverteilung
  Dim Schiefe As Decimal = _
    (P90 - 2 * Median + P10) / DezilDifferenz
 
  ' Exzess der Datenverteilung - erst sinnvoll ab ca. N=100
  ' (relativ zur Normalverteilung)
  Dim Exzess As Decimal = _
    CDec(0.263) - Quartilabstand / DezilDifferenz
End Sub

Das Modul mit der Erweiterungsmethode und den Hilfsfunktionen:

Public Module modPerzentile
 
  ''' <summary>
  ''' Berechnung des Perzentils einer Verteilung 
  ''' </summary>
  ''' <typeparam name="T">EntryType (Primitive)</typeparam>
  ''' <param name="Daten">Array mit Daten</param>
  ''' <param name="Percentil">Perzentil (von 1 bis 99)</param>
  ''' <param name="MinValue">optional: kleinster gültiger Wert</param>
  ''' <param name="MaxValue">optional: größter gültiger Wert</param>
  ''' <returns>Perzentil-Wert</returns>
  <Runtime.CompilerServices.Extension()> _
  Public Function Percentil_Val(Of T As Structure) ( _
    ByVal Daten() As T, ByVal Percentil As T, _
    Optional ByVal MinValue As Double = Double.NaN, _
    Optional ByVal MaxValue As Double = Double.NaN) As T
 
    ' Entry-Parameter in Double wandeln und prüfen
    Dim iPercentil As Double = GetDouble(Percentil)
 
    If iPercentil < 1 Or iPercentil > 99 Then
      Throw New ArgumentException _
        ("Percentil-Parameter: nicht zwischen 1 und 99")
    End If
 
    ' Array filtern und sortieren
    Dim iDaten() As T = FilterAndSort(Daten, MinValue, MaxValue)
 
    ' Perzentil bestimmen: Anzahl plausibler Daten
    Dim N As Integer = iDaten.Length
    ' Perzentil-Rang nach Formel berechnen
    Dim RN As Double = iPercentil * (N + 1) / 100
    ' Benachbarte reale Ränge ermitteln 
    Dim RNU As Integer = CInt(Math.Max(Math.Floor(RN), 1))
    Dim RNO As Integer = CInt(Math.Min(Math.Ceiling(RN), N))
    ' Daten an der Position dieser Ränge lesen
    ' --> nullbasiertes Array !!
    Dim elem_rnu As T = iDaten(RNU - 1)
    Dim elem_rno As T = iDaten(RNO - 1)
 
    ' Double-Werte für Berechnungen und Vergleich
    Dim dbl_rnu As Double = GetDouble(elem_rnu)
    Dim dbl_rno As Double = GetDouble(elem_rno)
 
    ' Ist Interpolation erforderlich?
    If dbl_rnu < dbl_rno Then
      ' Lineare Interpolation auf Basis 'Double'
      Dim dbl_interpol As Double = dbl_rnu + _
        (RN - RNU) / (RNO - RNU) * (dbl_rno - dbl_rnu)
      GetEntry(dbl_interpol, elem_rnu)
    End If
 
    ' Rückgabe des Perzentil-Wertes
  Return elem_rnu
End Function
  ''' <summary>Filtert/Sortiert Daten in einem 1D-Array 
  ''' (mindestens 10 plausible Daten)</summary>
  ''' <typeparam name="T">Typ: Primitive/Decimal</typeparam>
  ''' <param name="arr">Daten-Array</param>
  ''' <param name="MinValue">Untergrenze plausibler Werte</param>
  ''' <param name="MaxValue">Obergrenze plausibler Werte</param>
  ''' <returns>bearbeitetes Array</returns>
  <Runtime.CompilerServices.Extension()> _
  Public Function FilterAndSort(Of T As Structure) ( _
    ByVal arr() As T, _
    Optional ByVal MinValue As Double = Double.NaN, _
    Optional ByVal MaxValue As Double = Double.NaN) As T()
 
    ' Die generische Funktion checkt den Arraytyp, lokalisiert
    ' die Arrayelemente, entfernt bei IEEE-Typen ggf. die
    ' Sonderwerte, bearbeitet die optionalen Limits 
    ' und sortiert die Rest-Daten im Array
 
    ' erst mal prüfen ...
    Check_Array(arr)
 
    ' optional vorgegebene Filter einrichten
    ' ggf. die IEEE-Sonderwerte ausfiltern (auch Single!)
    If Double.IsNaN(MinValue) Then MinValue = Double.MinValue
    If Double.IsNaN(MaxValue) Then MaxValue = Double.MaxValue
 
    ' Linq-Abfrage der Daten: Filtern & Sortieren
    Dim iarr(arr.Length - 1) As T
    iarr = (From elem As T In arr _
      Let dbl As Double = GetDouble(elem) _
      Where dbl >= MinValue And dbl <= MaxValue _
      Order By elem Ascending _
      Select elem).ToArray
 
    ' Bleiben genügend Daten übrig?
    If iarr.Length < 10 Then
      Throw New ArgumentException _
        ("Filter: Weniger als 10 verwertbare Daten")
    End If
 
    ' Rückgabe des gefilterten und sortierten Array 
    Return iarr
  End Function
  Private Function GetDouble(Of T)(ByVal elem As T) As Double
    ' Ein Entry-Wert wir in einen Double-Wert gewandelt
    Return CType(Convert.ChangeType(elem, _
      TypeCode.Double), Double)
  End Function
  Private Sub GetEntry(Of T)(ByVal elem As Double, _
    ByRef Entry As T)
 
    ' Ein Double-Wert wird in einen Entry-Wert gewandelt
    Entry = CType(Convert.ChangeType(elem, GetType(T)), T)
  End Sub
  Private Sub Check_Array(Of T)(ByVal arr() As T)
    ' Routine überprüft Vorhandensein, Datentyp und
    ' Zahl der Array-Elemente
    Const dec_str As String = "System.Decimal"
    If arr Is Nothing Then
      Throw New ArgumentNullException _
        ("Array-Parameter: Nothing")
    End If
    If Not GetType(T).IsPrimitive And _
      Not GetType(T).ToString = dec_str Then
      Throw New ArgumentException _
        ("Array-Elemente: keine Primitives bzw. Decimals")
    End If
    If arr.Length < 10 Then
      Throw New ArgumentException _
        ("Array-Check: Weniger als 10 Elemente")
    End If
  End Sub
  ''' <summary>Hilfsfunktion: 
  ''' Füllt ein num.Array mit Zufallszahlen (-/+10000)</summary>
  ''' <typeparam name="T">Datentyp</typeparam>
  ''' <param name="arr">zu füllendes Array</param>
  <System.Runtime.CompilerServices.Extension()> _
  Public Sub Fill_Array(Of T)(ByRef arr As T())
    For i As Integer = 0 To arr.GetUpperBound(0)
      arr(i) = CType(Convert.ChangeType(RandomNumber, GetType(T)), T)
    Next i
  End Sub
  Private Function RandomNumber() As Double
    Dim r As Double = 10000.0# * Microsoft.VisualBasic.Rnd
    If Microsoft.VisualBasic.Rnd < 0.5 Then r *= -1
    Return r
  End Function
End Module



Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Tipps & Tricks finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
 
Copyright ©2000-2024 vb@rchiv Dieter OtterAlle Rechte vorbehalten.


Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.