vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#
Zippen wie die Profis!  
 vb@rchiv Quick-Search: Suche startenErweiterte Suche starten   Impressum  | Datenschutz  | vb@rchiv CD Vol.6  | Shop Copyright ©2000-2024
 
zurück
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:     [ Jetzt bewerten ]Views:  13.902 
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

Dieser Tipp wurde bereits 13.902 mal aufgerufen.

Voriger Tipp   |   Zufälliger Tipp   |   Nächster Tipp

Über diesen Tipp im Forum diskutieren
Haben Sie Fragen oder Anregungen zu diesem Tipp, können Sie gerne mit anderen darüber in unserem Forum diskutieren.

Neue Diskussion eröffnen

nach obenzurück


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.
 
   

Druckansicht Druckansicht Copyright ©2000-2024 vb@rchiv Dieter Otter
Alle 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.

Diese Seiten wurden optimiert für eine Bildschirmauflösung von mind. 1280x1024 Pixel