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 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: 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.905 mal aufgerufen. Voriger Tipp | Zufälliger Tipp | Nächster Tipp
Anzeige
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. |
Neu! sevPopUp 2.0 Dynamische Kontextmenüs! Erstellen Sie mit nur wenigen Zeilen Code Kontextmenüs dynamisch zur Laufzeit. Vordefinierte Styles (XP, Office, OfficeXP, Vista oder Windows 8) erleichtern die Anpassung an die eigenen Anwendung... Tipp des Monats April 2024 Skyfloy Chart von Microsoft und dazu noch gratis Tutorial für Microsoft Chart Controls für Microsoft .NET Framework 3.5 Access-Tools Vol.1 Über 400 MByte Inhalt Mehr als 250 Access-Beispiele, 25 Add-Ins und ActiveX-Komponenten, 16 VB-Projekt inkl. Source, mehr als 320 Tipps & Tricks für Access und VB |
||||||||||||||||
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. |