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 14.302 mal aufgerufen.
Anzeige
![]() ![]() ![]() 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. |
sevISDN 1.0 ![]() Überwachung aller eingehender Anrufe! Die DLL erkennt alle über die CAPI-Schnittstelle eingehenden Anrufe und teilt Ihnen sogar mit, aus welchem Ortsbereich der Anruf stammt. Weitere Highlights: Online-Rufident, Erkennung der Anrufbehandlung u.v.m. Tipp des Monats ![]() Manfred Bohn IndexOf für mehrdimensionale Arrays Die generische Funktion "IndexOf" ermittelt das erste Auftreten eines bestimmten Wertes in einem n-dimensionalen Array Neu! sevEingabe 3.0 ![]() Einfach stark! Ein einziges Eingabe-Control für alle benötigten Eingabetypen und -formate, inkl. Kalender-, Taschenrechner und Floskelfunktion, mehrspaltige ComboBox mit DB-Anbindung, ImageComboBox u.v.m. |
||||||||||||||||
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. |