Tiefe neuronale Netzwerke (Teil I). Datenaufbereitung

Vladimir Perervenko | 15 September, 2017

Dieser Artikel setzt das Thema "Tiefe neuronale Netzwerke" (Deep Neural Networks, DNN) fort, das ich in den vorherigen Artikeln behandelt habe (1, 2, 3) und entwickelt es weiter.

DNN sind weit verbreitet und werden in verschiedenen Bereichen intensiv verwendet. Treffende Beispiele für die alltägliche Anwendung sind die Erkennung von Mustern, Spracherkennung und automatische Übersetzung. Die DNN werden auch im Trading verwendet, aus diesem Grund halte ich eine vertiefte Untersuchung von DNN vor dem Hintergrund einer schnellen Entwicklung des Algotradings für sehr nützlich und aussichtsreich.

In den letzten Jahren konnten Forscher viele neue Ideen, Methoden, Ansätze und Hypothesen hinsichtlich der Verwendung von DNN aufstellen und beweisen. In dieser Artikelserie werden der Entwicklungsstand und Hauptrichtungen des Themas betrachtet, verschiedene Ideen und Methoden werden anhand praktischer Experimente überprüft, darüber hinaus werden wir uns mit qualitativen Werten von DNN auseinandersetzen. In unserer Arbeit werden wir nur mehrschichtige voll verknüpfte neuronale Netze verwenden.

Die Artikelserie hat vier Schwerpunkte: 

  • Aufbereitung, Auswertung und Bestätigung von Inputdaten durch verschiedene Transformationen.
  • Neue Möglichkeiten des Pakets darch (v.0.12). Flexibilität und erweiterte Möglichkeiten.
  • Bestätigung der Ergebnisse der Prognose (Optimieren von Hyperparametern von DNN und Ensembles aus neuronalen Netzwerken).
  • Grafische Möglichkeiten für die Kontrolle eines Expert Advisors sowohl während des Trainierens, als auch während der Arbeit. 

Der vorliegende Artikel beschäftigt sich mit der Aufbereitung von Daten, die im Handelsterminal erhalten wurden, für die Verwendung im neuronalen Netzwerk.

Inhaltsverzeichnis

Einleitung

Die Entwicklung, das Trainieren und Testen eines tiefen neuronalen Netzes besteht aus Schritten, die eine strenge Reihenfolge haben. Wie die Erstellung eines jeden Modells des Maschinellen Lernens kann die Erstellung eines DNN auch in zwei ungleiche Teile geteilt:

  • Aufbereiten von Ein- und Ausgabedaten für Experimente;
  • Erstellen, Trainieren, Testen und Optimieren von Parametern des Modells.

Die erste Phase nimmt die meiste Zeit der Umsetzung des Projekts in Anspruch: ca. 70%. Die Arbeitsergebnis des Modells hängt zu 80% von den Ergebnissen dieser Phase ab. Wie bekannt, "Garbage in, garbage out". Deswegen betrachten wir den Inhalt der Arbeit sehr ausführlich.

Um die Experimente zu wiederholen ist es notwendig, MRO 3.4.0 und Rstudio zu installieren. Wir werden auf die Installierung der Software nicht eingehen, weil es genug Informationen darüber im Internet und in den angehängten Dateien gibt.


Die Sprache R

Rufen wir uns einige wichtige Informationen über die Sprache R ins Gedächtnis. Das ist eine Programmiersprache sowie eine Umgebung für statistische Berechnungen und Zeichnen von Charts, die 1996 von neuseeländischen Wissenschaftlern Ross Ihaka und Robert Gentleman an der Universität Auckland entwickelt wurde. R ist ein GNU-Projekt, d.h. eine freie Software. Seine Philosophie läuft auf die folgenden Grundsätze (Freiheiten) hinaus:

  • die Freiheit, Programme für jeden Zweck auszuführen (Freiheit 0);
  • die Freiheit, die Funktionsweise eines Programms zu untersuchen, und es an seine Bedürfnisse anzupassen (Freiheit 1);
  • die Freiheit, Kopien weiterzugeben und damit seinen Mitmenschen zu helfen (Freiheit 2);
  • Freiheit, ein Programm zu verbessern, und die Verbesserungen an die Öffentlichkeit weiterzugeben, sodass die gesamte Gesellschaft profitiert.

Heute wird R hauptsächlich von "R Development Core Team" und dem im letzten Jahr gegründeten R Consortium verbessert und entwickelt. Die Liste der Mitglieder des Zusammenschlusses (IBM, Microsoft, Rstudio, Google, Mango, Oracle u.a.) zeugt von einer großen Unterstützung, einem starken Interesse an der Anwendung und von guten Aussichten der Sprache in der Zukunft. 

Die Vorteile der Sprache R:

  • Heutzutage ist die Sprache R de facto der Standard der statistischen Berechnungen.
  • Die Sprache wird von der internationalen Wissenscahftsgemeinschaft entwickelt und unterstützt.
  • Vielfältige Pakete hinsichtlich aller innovativen Richtungen des Data-Mining. Dabei vergehen zwischen der Veröffentlichung einer neuen Idee und deren Implementierung in R höchstens zwei Wochen.
  • Zu guter Letzt ist seine Verwendung kostenlos.

1. Erstellung des Ausgangstadensatzes

"Die ganzen vorherigen, aktuellen und künftigen Preisbewegungen sind im Preis selbst enthalten"

Es gibt eine Vielzahl von Methoden (Paketen) für Aufbereitung, Auswertung und Auswahl von Prädiktoren. Eine Übersicht dieser Methoden ist in [1] angeführt. Ihre Vielfalt lässt sich durch die Vielfalt der Daten in der realen Welt erklären. Der verwendete Datentyp definiert die Methoden der Forschung und Verarbeitung.

Finanzdaten, die wir untersuchen, stellen hierarchische regelmäßige Zeitreihen dar, die "unendlich" und einfach zu extrahieren sind. Die Basis-Reihe — das sind OHLCV-Kurse für ein Symbols in einem konkreten Zeitrahmen.

Auf der Basis-Reihe beruhen alle weiteren Zeitreihen:

  • nichtparametrische — zum Beispiel, x^2, sqrt(abs(x)), x^3, -x^2 usw.
  • funktionell nichtparametrische — sin(2*n*x), ln(abs(x)), log(Pr(t)/Pr(t-1)) usw.
  • parametrische — eine Vielzahl verschiedener Indikatoren, die hauptsächlich als Prädiktoren verwendet werden. Das können sowohl Oszillatoren als auch verschiedene Filter sein.

Als Zielvariable werden entweder Indikatoren, die Signale (Faktoren) erzeugen, oder eine Reihe von Bedingungen verwendet, deren Erfüllung ein Signal gibt.

1.1. Kurse

Die Kurse OHLC, Volume und time erhalten wir aus dem Terminal als Vektoren (o, h, l, cl, v, d). Schreiben wir eine Funktion, die die Vektoren vereinigt, die wir aus dem Terminal in dataFrame erhalten haben. Dabei ändern wir die Zeit des Anfangs des Balkens in POSIXct.

#---pr.OHLCV-------------------
pr.OHLCV <- function(d, o,  h,  l,  cl, v){
# (d, o,  h,  l,  cl, v)- vector
  require('magrittr')
  require('dplyr')
  require('anytime')
  price <- cbind(Data = rev(d), 
                 Open = rev(o), High = rev(h), 
                 Low = rev(l), Close = rev(cl),
                 Vol = rev(v)) %>% as.tibble()  
  price$Data %<>% anytime(., tz = "CET") 
  return(price)
}

Da wir die Vektoren der Kurse in die Umgebung env geladen haben, berechnen wir dataFrame pr und beseitigen unnötige Variablen aus der Umgebung env:

evalq({pr <- pr.OHLCV(Data, Open, High, Low, Close, Volume)
       rm(list = c("Data", "Open", "High", "Low", "Close", "Volume"))
       }, 
env)
Schauen wir, wie dataFrame am Anfang aussieht:
> head(env$pr)
# A tibble: 6 x 6
                 Data    Open    High     Low   Close
               <dttm>   <dbl>   <dbl>   <dbl>   <dbl>
1 2017-01-10 11:00:00 122.758 122.893 122.746 122.859
2 2017-01-10 11:15:00 122.860 122.924 122.818 122.848
3 2017-01-10 11:30:00 122.850 122.856 122.705 122.720
4 2017-01-10 11:45:00 122.721 122.737 122.654 122.693
5 2017-01-10 12:00:00 122.692 122.850 122.692 122.818
6 2017-01-10 12:15:00 122.820 122.937 122.785 122.920
# ... with 1 more variables: Vol <dbl>

und am Ende:

> tail(env$pr)
# A tibble: 6 x 6
                 Data    Open    High     Low   Close
               <dttm>   <dbl>   <dbl>   <dbl>   <dbl>
1 2017-05-05 20:30:00 123.795 123.895 123.780 123.888
2 2017-05-05 20:45:00 123.889 123.893 123.813 123.831
3 2017-05-05 21:00:00 123.833 123.934 123.825 123.916
4 2017-05-05 21:15:00 123.914 123.938 123.851 123.858
5 2017-05-05 21:30:00 123.859 123.864 123.781 123.781
6 2017-05-05 21:45:00 123.779 123.864 123.781 123.781
# ... with 1 more variables: Vol <dbl>

Wir haben also 8000 Balken von 10.01.2017 bis 05.05.2017. Fügen wir dem Datenrahmen pr abgeleitete Preise — Medium PriceTypical Price und Weighted Close hinzu.

evalq(pr %<>% mutate(.,
                  Med = (High + Low)/2,
                  Typ = (High + Low + Close)/3,
                  Wg  = (High + Low + 2 * Close)/4,
                  #CO  = Close - Open,
                  #HO  = High - Open,
                  #LO  = Low - Open,
                  dH  = c(NA, diff(High)),
                  dL  = c(NA, diff(Low))
                  ), 
      env) 

1.2. Prädiktoren

Im Vergleich zu den vorherigen Experimenten, haben wir den Set von Prädiktoren vereinfacht. Ihre Rolle übernehmen die Zeitfilter FATL, SATL, RFTL, RSTL. Die Zeitfilter wurden im Artikel "New Adaptive Method of Following the Tendency and Market Cycles" von V. Kravchuk ausführlich beschrieben, den Sie im Anhang finden (s. das Kapitel "Neue Werkzeuge der technischen Analyse und deren Interpretation). Listen wir diese kurz auf.

  • FATL — Fast Adaptive Trend Line;
  • SATL — Slow Adaptive Trend Line;
  • RFTL — Reference Fast Trend Line;
  • RSTL — Reference Slow Trend Line.

Die Änderungsrate von FATL und SATL wird nach den Indikatoren FTLM (Fast Trend Line Momentum) und STLM (Slow Trend Line Momentum) überwacht.

Die weiteren benötigten technischen Werkzeuge sind zwei Oszillatoren — die Indexe RBCI und PCCI. Der Index RBCI, Range Bound Channel Index, ist ein Channel Index mit begrenzter Bandbreite, der mit einem Bandpassfilter berechnet wird (der Filter löscht einen tieffrequenten Trend und ein hochfrequentes Rauschen). Der Index PCCI — Perfect Commodity Channel Index.

Die Funktion, die die Zahlenfilter FATL, SATL, RFTL, RSTL, berechnet, sieht wie folgt aus:

#-----DigFiltr-------------------------
DigFiltr <- function(X, type = 1){
# X - vector
  require(rowr)
  fatl <- c( +0.4360409450, +0.3658689069, +0.2460452079, +0.1104506886, -0.0054034585,
             -0.0760367731, -0.0933058722, -0.0670110374, -0.0190795053, +0.0259609206,
             +0.0502044896, +0.0477818607, +0.0249252327, -0.0047706151, -0.0272432537,
             -0.0338917071, -0.0244141482, -0.0055774838, +0.0128149838, +0.0226522218,
             +0.0208778257, +0.0100299086, -0.0036771622, -0.0136744850, -0.0160483392,
             -0.0108597376, -0.0016060704, +0.0069480557, +0.0110573605, +0.0095711419,
             +0.0040444064, -0.0023824623, -0.0067093714, -0.0072003400, -0.0047717710,
             0.0005541115, 0.0007860160, 0.0130129076, 0.0040364019 )
  rftl <- c(-0.0025097319, +0.0513007762 , +0.1142800493 , +0.1699342860 , +0.2025269304 ,
            +0.2025269304, +0.1699342860 , +0.1142800493 , +0.0513007762 , -0.0025097319 ,
            -0.0353166244, -0.0433375629 , -0.0311244617 , -0.0088618137 , +0.0120580088 ,
            +0.0233183633, +0.0221931304 , +0.0115769653 , -0.0022157966 , -0.0126536111 ,
            -0.0157416029, -0.0113395830 , -0.0025905610 , +0.0059521459 , +0.0105212252 ,
            +0.0096970755, +0.0046585685 , -0.0017079230 , -0.0063513565 , -0.0074539350 ,
            -0.0050439973, -0.0007459678 , +0.0032271474 , +0.0051357867 , +0.0044454862 ,
            +0.0018784961, -0.0011065767 , -0.0031162862 , -0.0033443253 , -0.0022163335 ,
            +0.0002573669, +0.0003650790 , +0.0060440751 , +0.0018747783)
  satl <- c(+0.0982862174, +0.0975682269 , +0.0961401078 , +0.0940230544, +0.0912437090 ,
            +0.0878391006, +0.0838544303 , +0.0793406350 ,+0.0743569346 ,+0.0689666682 ,
            +0.0632381578 ,+0.0572428925 , +0.0510534242,+0.0447468229, +0.0383959950, 
            +0.0320735368, +0.0258537721 ,+0.0198005183 , +0.0139807863,+0.0084512448, 
            +0.0032639979, -0.0015350359, -0.0059060082 ,-0.0098190256 , -0.0132507215,
            -0.0161875265, -0.0186164872, -0.0205446727, -0.0219739146 ,-0.0229204861 ,
            -0.0234080863,-0.0234566315, -0.0231017777, -0.0223796900, -0.0213300463 ,-0.0199924534 ,
            -0.0184126992,-0.0166377699, -0.0147139428, -0.0126796776, -0.0105938331 ,-0.0084736770 ,
            -0.0063841850,-0.0043466731, -0.0023956944, -0.0005535180, +0.0011421469 ,+0.0026845693 ,
            +0.0040471369,+0.0052380201, +0.0062194591, +0.0070340085, +0.0076266453 ,+0.0080376628 ,
            +0.0083037666,+0.0083694798, +0.0082901022, +0.0080741359, +0.0077543820 ,+0.0073260526 ,
            +0.0068163569,+0.0062325477, +0.0056078229, +0.0049516078, +0.0161380976 )
  rstl <- c(-0.0074151919,-0.0060698985,-0.0044979052,-0.0027054278,-0.0007031702,+0.0014951741,
            +0.0038713513,+0.0064043271,+0.0090702334,+0.0118431116,+0.0146922652,+0.0175884606, 
            +0.0204976517,+0.0233865835,+0.0262218588,+0.0289681736,+0.0315922931,+0.0340614696,
            +0.0363444061,+0.0384120882,+0.0402373884,+0.0417969735,+0.0430701377,+0.0440399188,
            +0.0446941124,+0.0450230100,+0.0450230100,+0.0446941124,+0.0440399188,+0.0430701377,
            +0.0417969735,+0.0402373884,+0.0384120882,+0.0363444061,+0.0340614696,+0.0315922931,
            +0.0289681736,+0.0262218588,+0.0233865835,+0.0204976517,+0.0175884606,+0.0146922652,
            +0.0118431116,+0.0090702334,+0.0064043271,+0.0038713513,+0.0014951741,-0.0007031702,
            -0.0027054278,-0.0044979052,-0.0060698985,-0.0074151919,-0.0085278517,-0.0094111161,
            -0.0100658241,-0.0104994302,-0.0107227904,-0.0107450280,-0.0105824763,-0.0102517019,
            -0.0097708805,-0.0091581551,-0.0084345004,-0.0076214397,-0.0067401718,-0.0058083144,
            -0.0048528295,-0.0038816271,-0.0029244713,-0.0019911267,-0.0010974211,-0.0002535559,
            +0.0005231953,+0.0012297491,+0.0018539149,+0.0023994354,+0.0028490136,+0.0032221429,
            +0.0034936183,+0.0036818974,+0.0038037944,+0.0038338964,+0.0037975350,+0.0036986051,
            +0.0035521320,+0.0033559226,+0.0031224409,+0.0028550092,+0.0025688349,+0.0022682355, 
            +0.0073925495)
  if (type == 1) {k = fatl} 
  if (type == 2) {k = rftl} 
  if (type == 3) {k = satl}
  if (type == 4) {k = rstl}
  n <- length(k)
  m <- length(X)
  k <- rev(k)
  f <- rowr::rollApply(data = X, 
                       fun = function(x) {sum(x * k)},
                       window = n, minimum = n, align = "right")
  while (length(f) < m) { f <- c(NA,f)}
  return(f)
}

Berechnen und fügen wir diese dem Datenrahmen pr hinzu

evalq(pr %<>% mutate(.,
                   fatl = DigFiltr(Close, 1),
                   rftl = DigFiltr(Close, 2),
                   satl = DigFiltr(Close, 3),
                   rstl = DigFiltr(Close, 4)
                   ),
      env) 

Fügen wir die Oszillatoren FTLM, STLM, RBCI, PCCI, ihre ersten Differenzen und die ersten Differenzen zwischen den Zahlenfiltern dem Dataframe pr hinzu:

evalq(pr %<>% mutate(.,
                     ftlm = fatl - rftl,
                     rbci = fatl - satl,
                     stlm = satl - rstl,
                     pcci = Close - fatl,
                     v.fatl = c(NA, diff(fatl)),
                     v.rftl = c(NA, diff(rftl)),
                     v.satl = c(NA, diff(satl)),
                     v.rstl = c(NA, diff(rstl)*10)
                     ),
      env)
evalq(pr %<>% mutate(.,
                     v.ftlm = c(NA, diff(ftlm)),
                     v.stlm = c(NA, diff(stlm)),
                     v.rbci = c(NA, diff(rbci)),
                     v.pcci = c(NA, diff(pcci))
                    ),
      env)

1.3. Zielvariable

Als Indikator, der die Zielvariable erzeugt, werden wir den ZigZag() verwenden.

Die Funktion für dessen Berechnung erhält eine Zeitreihe und zwei Parameter: die minimale Länge einer Zigzag-Kurve (int oder double) und einen Preistyp für die Berechnung (Close, Med, Typ, Wd, mit (High, Low) ).

#------ZZ-----------------------------------
par <- c(25, 5)
ZZ <- function(x, par) {
# x - vector
  require(TTR)
  require(magrittr)
  ch = par[1] 
  mode = par[2]
  if (ch > 1) ch <- ch/(10 ^ (Dig - 1))
  switch(mode, xx <- x$Close,
         xx <- x$Med, xx <- x$Typ,
         xx <- x$Wd, xx <- x %>% select(High,Low))
  zz <- ZigZag(xx, change = ch, percent = F, 
               retrace = F, lastExtreme = T)
  n <- 1:length(zz)
  for (i in n) { if (is.na(zz[i])) zz[i] = zz[i - 1]}
  return(zz)
}

Berechnen wir den ZigZag, die erste Differenz, das Vorzeichen der ersten Differenz und fügen wir diese dem Datenrahmen pr hinzu:

evalq(pr %<>% cbind(., zigz = ZZ(., par = par)), env)
evalq(pr %<>% cbind(., dz = diff(pr$zigz) %>% c(NA, .)), env) 
evalq(pr %<>% cbind(., sig = sign(pr$dz)), env)

1.4. Ausgangsdatensatz

Fassen wir kurz zusammen, was und wie wir infolge der vorherigen Berechnungen erhalten haben.

Wir haben die Vektoren OHLCV und das Zeit-Label des Anfangs des Balkens auf М15 für EURJPY aus dem Terminal erhalten. Basierend auf diesen Daten wurde der Datenrahmen pr gebildet, zu welchem die Variablen FATL, SATL, RFTL, RSTL, FTLM, STLM, RBCI, PCCI und deren ersten Differenzen hinzugefügt wurden. Darüber hinaus wurde dem Datenrahmen der ZigZag mit der minimalen Länge der Zigzag-Kurve von 25 Punkten (4 Zeichen), seine erste Differenz und das Vorzeichen der ersten Differenz (-1,1), das als Signal verwendet wird.

Alle Daten wurden nicht in die globale Umgebung hochgeladen, sondern in die neue Umgebung env, in welcher wir alle weiteren Berechnungen durchführen werden. Solche Aufteilung erlaubt es, Datensätze verschiedener Symbole oder Zeitrahmen ohne Namenskonflikte bei der Berechnung zu verwenden.

Die Struktur des Datenrahmens pr ist unten angeführt. Die für die weiteren Berechnungen benötigten Variablen können da ausgewählt werden.

str(env$pr)
'data.frame':   8000 obs. of  30 variables:
 $ Data  : POSIXct, format: "2017-01-10 11:00:00" ...
 $ Open  : num  123 123 123 123 123 ...
 $ High  : num  123 123 123 123 123 ...
 $ Low   : num  123 123 123 123 123 ...
 $ Close : num  123 123 123 123 123 ...
 $ Vol   : num  3830 3360 3220 3241 3071 ...
 $ Med   : num  123 123 123 123 123 ...
 $ Typ   : num  123 123 123 123 123 ...
 $ Wg    : num  123 123 123 123 123 ...
 $ dH    : num  NA 0.031 -0.068 -0.119 0.113 ...
 $ dL    : num  NA 0.072 -0.113 -0.051 0.038 ...
 $ fatl  : num  NA NA NA NA NA NA NA NA NA NA ...
 $ rftl  : num  NA NA NA NA NA NA NA NA NA NA ...
 $ satl  : num  NA NA NA NA NA NA NA NA NA NA ...
 $ rstl  : num  NA NA NA NA NA NA NA NA NA NA ...
 $ ftlm  : num  NA NA NA NA NA NA NA NA NA NA ...
 $ rbci  : num  NA NA NA NA NA NA NA NA NA NA ...
 $ stlm  : num  NA NA NA NA NA NA NA NA NA NA ...
 $ pcci  : num  NA NA NA NA NA NA NA NA NA NA ...
 $ v.fatl: num  NA NA NA NA NA NA NA NA NA NA ...
 $ v.rftl: num  NA NA NA NA NA NA NA NA NA NA ...
 $ v.satl: num  NA NA NA NA NA NA NA NA NA NA ...
 $ v.rstl: num  NA NA NA NA NA NA NA NA NA NA ...
 $ v.ftlm: num  NA NA NA NA NA NA NA NA NA NA ...
 $ v.stlm: num  NA NA NA NA NA NA NA NA NA NA ...
 $ v.rbci: num  NA NA NA NA NA NA NA NA NA NA ...
 $ v.pcci: num  NA NA NA NA NA NA NA NA NA NA ...
 $ zigz  : num  123 123 123 123 123 ...
 $ dz    : num  NA -0.0162 -0.0162 -0.0162 -0.0162 ...
 $ sig   : num  NA -1 -1 -1 -1 -1 -1 -1 -1 -1 ...

Wählen wir alle vorher berechneten Prädiktoren in dataSet aus. Die Zielvariable sig wandeln wir in einen Faktor um und verschieben um einen Balken in die Zukunft.

evalq(dataSet <- pr %>% tbl_df() %>%
        dplyr::select(Data, ftlm, stlm, rbci, pcci,
                      v.fatl, v.satl, v.rftl, v.rstl,
                      v.ftlm, v.stlm, v.rbci, v.pcci, sig) %>%
        dplyr::filter(., sig != 0) %>% 
        mutate(., Class = factor(sig, ordered = F) %>% 
                 dplyr::lead()) %>% 
        dplyr::select(-sig),
      env)

Visuelle Datenanalyse

Zeichnen wir einen OHLC-Chart unter Verwendung des Pakets ggplot2. Nehmen wir die Daten für die letzten zwei Tage und zeichnen wir den Kurschart in Balken.

evalq(pr %>% tail(., 200) %>%
        ggplot(aes(x = Data, y = Close)) +
        geom_candlestick(aes(open = Open, high = High, low = Low, close = Close)) +
        labs(title = "EURJPY Candlestick Chart", y = "Close Price", x = "") + 
        theme_tq(), env)

Ris1

Abb.1. Kurschart

Zeichnen wir einen Chart von FATL, SATL, RFTL, RSTL und ZZ:

evalq(pr %>% tail(., 200) %>%
        ggplot(aes(x = Data, y = Close)) +
        geom_candlestick(aes(open = Open, high = High, low = Low, close = Close)) +
        geom_line(aes(Data, fatl), color = "steelblue", size = 1) +
        geom_line(aes(Data, rftl), color = "red", size = 1) +
        geom_line(aes(Data, satl), color = "gold", size = 1) +
        geom_line(aes(Data, rstl), color = "green", size = 1) +
        geom_line(aes(Data, zigz), color = "black", size = 1) +
        labs(title = "EURJPY Candlestick Chart", 
             subtitle = "Combining Chart Geoms",
             y = "Close Price", x = "") + 
        theme_tq(), env)

Ris2

Abb.2. FATL, SATL, RFTL, RSTL und ZZ

Teilen wir die Oszillatoren in drei Gruppen für eine einfachere Anzeige auf.

require(dygraphs)
evalq(dataSet %>% tail(., 200) %>% tk_tbl %>%
        select(Data, ftlm, stlm, rbci, pcci) %>%
        tk_xts() %>% 
        dygraph(., main = "Oscilator base") %>% 
        dyOptions(., 
                  fillGraph = TRUE, 
                  fillAlpha = 0.2,
                  drawGapEdgePoints = TRUE,
                  colors = c("green", "violet", "red", "blue"),
                  digitsAfterDecimal = Dig) %>%
        dyLegend(show = "always", 
                 hideOnMouseOut = TRUE), 
      env)

Ris3

Abb.3. Basis-Oszillatoren

evalq(dataSet %>% tail(., 200) %>% tk_tbl %>%
        select(Data, v.fatl, v.satl, v.rftl, v.rstl) %>%
        tk_xts() %>% 
        dygraph(., main = "Oscilator 2") %>% 
        dyOptions(., 
                  fillGraph = TRUE, 
                  fillAlpha = 0.2,
                  drawGapEdgePoints = TRUE,
                  colors = c("green", "violet", "red", "darkblue"),
                  digitsAfterDecimal = Dig) %>%
        dyLegend(show = "always", 
                 hideOnMouseOut = TRUE), 
      env)

Ris4

Abb.4. Oszillatoren der 2. Gruppe

Die Oszillatoren der dritten Gruppe werden auf den letzten 100 Balken eingezeichnet:

evalq(dataSet %>% tail(., 100) %>% tk_tbl %>%
        select(Data, v.ftlm, v.stlm, v.rbci, v.pcci) %>%
        tk_xts() %>% 
        dygraph(., main = "Oscilator 3") %>% 
        dyOptions(., 
                  fillGraph = TRUE, 
                  fillAlpha = 0.2,
                  drawGapEdgePoints = TRUE,
                  colors = c("green", "violet", "red", "darkblue"),
                  digitsAfterDecimal = Dig) %>%
        dyLegend(show = "always", 
                 hideOnMouseOut = TRUE), 
      env)

Ris5

Abb.5. Oszillatoren der 3 Gruppe

2. Explorative Datenanalyse (EDA)

"There are no routine statistical questions, only questionable statistical routines." - Sir David R. Cox

“Viel besser eine ungefähre Antwort auf die richtige Frage, die oft vage ist, als eine exakte Antwort auf eine falsche Frage, die immer genauer formuliert werden kann.” — John Tukey

Wir verwenden die explorative Datenanalyse um, das Verständnis unserer Daten zu entwickeln. Die einfachste Methode ist Fragen als Werkzeug der Untersuchung zu verwenden. Wenn wir eine Frage stellen, konzentrieren wir uns auf einem bestimmten Datenteil. Dies hilft uns zu entscheiden, welche Charts, Modelle oder Transformationen wir verwenden sollten.

EDA ist ein kreativer Prozess. Wie in den meisten kreativen Prozessen ist der Schlüssel zu einer guten Frage, noch mehr Fragen zu stellen. Es ist schwierig solche Fragen am Anfang der Analyse zu stellen, weil wir noch nicht wissen, welche Schlussfolgerungen im Datensatz enthalten sind. Von der anderen Seite hebt jede neue Frage einen anderen Aspekt hervor und erhöht die Chance, eine Entdeckung zu machen. Wir können schnell zu dem interessantesten Teil der Stichprobe übergehen und durch konsequente Fragen die Situation klären.

Es gibt keine Regeln, welche Fragen wir stellen müssen, um die Untersuchung durchzuführen. Aber zwei Typen von Fragen werden immer hilfreich sein:

  • Welcher Typ haben die Änderungen in meinen Variablen?
  • Welchen Typ haben die Kovariationen zwischen den Variablen?

Definieren wir den Hauptbegriff.

Variationen stellen die Variabilität der Werte einer Variablen von einer Dimension in die andere dar. Es gibt eine Vielzahl an Variationen im alltäglichen Leben. Wenn man eine kontinuierliche Variable sieben Mal hintereinander misst, erhält man sieben verschieden Ergebnisse. Das gilt auch wenn Sie konstante Größen messen (z.B. Lichtgeschwindigkeit). Jede der Dimensionen wird einen Fehler enthalten, der sich immer ändert. Die Variablen eines Typs können sich auch ändern, wenn man verschiedene konkrete Gegenstände (z.B. die Augenfarbe verschiedener Menschen) oder ein Gegenstand zu verschiedenen Zeitpunkten misst (z.B. das Energieniveau eines Elektrons zu unterschiedlichen Zeitpunkten). Jede Variable hat einen eigenen Charakter der Variationen, die eine interessante Information aufdecken können. Die beste Weise, diese Information zu verstehen, ist die Werteverteilung der Variablen zu visualisieren. Das ist gerade der Fall, wenn ein Bild mehr als tausend Worte sagt.

2.1. Gesamtstatistik

Die Gesamtstatistik unserer Zeitreihe kann man mithilfe der Funktion table.Stats()::PerformenceAnalitics verfolgen.

> table.Stats(env$dataSet %>% tk_xts())
Using column `Data` for date_var.
                     ftlm      stlm      rbci      pcci
Observations    7955.0000 7908.0000 7934.0000 7960.0000
NAs               42.0000   89.0000   63.0000   37.0000
Minimum           -0.7597   -1.0213   -0.9523   -0.5517
Quartile 1        -0.0556   -0.1602   -0.0636   -0.0245
Median            -0.0001    0.0062   -0.0016   -0.0001
Arithmetic Mean    0.0007    0.0025    0.0007    0.0001
Geometric Mean    -0.0062       NaN   -0.0084   -0.0011
Quartile 3         0.0562    0.1539    0.0675    0.0241
Maximum            2.7505    3.0407    2.3872    1.8859
SE Mean            0.0014    0.0033    0.0015    0.0006
LCL Mean (0.95)   -0.0020   -0.0040   -0.0022   -0.0010
UCL Mean (0.95)    0.0034    0.0090    0.0035    0.0012
Variance           0.0152    0.0858    0.0172    0.0026
Stdev              0.1231    0.2929    0.1311    0.0506
Skewness           4.2129    1.7842    2.3037    6.4718
Kurtosis          84.6116   16.7471   45.0133  247.4208
                   v.fatl    v.satl    v.rftl    v.rstl
Observations    7959.0000 7933.0000 7954.0000 7907.0000
NAs               38.0000   64.0000   43.0000   90.0000
Minimum           -0.3967   -0.0871   -0.1882   -0.4719
Quartile 1        -0.0225   -0.0111   -0.0142   -0.0759
Median            -0.0006    0.0003    0.0000    0.0024
Arithmetic Mean    0.0002    0.0002    0.0002    0.0011
Geometric Mean    -0.0009    0.0000   -0.0003   -0.0078
Quartile 3         0.0220    0.0110    0.0138    0.0751
Maximum            1.4832    0.3579    0.6513    1.3093
SE Mean            0.0005    0.0002    0.0003    0.0015
LCL Mean (0.95)   -0.0009   -0.0003   -0.0005   -0.0020
UCL Mean (0.95)    0.0012    0.0007    0.0009    0.0041
Variance           0.0023    0.0005    0.0009    0.0188
Stdev              0.0483    0.0219    0.0308    0.1372
Skewness           5.2643    2.6705    3.9472    1.5682
Kurtosis         145.8441   36.9378   74.4182   13.5724
                   v.ftlm    v.stlm    v.rbci    v.pcci
Observations    7954.0000 7907.0000 7933.0000 7959.0000
NAs               43.0000   90.0000   64.0000   38.0000
Minimum           -0.9500   -0.2055   -0.6361   -1.4732
Quartile 1        -0.0280   -0.0136   -0.0209   -0.0277
Median            -0.0002   -0.0001   -0.0004   -0.0002
Arithmetic Mean    0.0000    0.0001    0.0000    0.0000
Geometric Mean    -0.0018   -0.0003   -0.0009       NaN
Quartile 3         0.0273    0.0143    0.0207    0.0278
Maximum            1.4536    0.3852    1.1254    1.9978
SE Mean            0.0006    0.0003    0.0005    0.0006
LCL Mean (0.95)   -0.0012   -0.0005   -0.0009   -0.0013
UCL Mean (0.95)    0.0013    0.0007    0.0009    0.0013
Variance           0.0032    0.0007    0.0018    0.0034
Stdev              0.0561    0.0264    0.0427    0.0579
Skewness           1.2051    0.8513    2.0643    3.0207
Kurtosis          86.2425   23.0651   86.3768  233.1964

Was wir anhand dieser Tabelle sehen wir:

  • Alle Prädiktoren haben eine (relativ) kleine Anzahl undefinierter Variablen NA.
  • Alle Prädiktoren haben eine stark ausgeprägte Rechtsschiefe (skewness).
  • Alle Prädiktoren haben eine hohe Kurtosis (kurtosis).

2.2. Visualisierung der Gesamtstatistik

“Der größte Wert eines Bildes besteht darin, dass es uns darauf aufmerksam macht, was wir nie erwartet haben zu sehen”. — John Tukey

Schauen wir uns die Variation und die Kovariation unserer Variablen im Datensatz dataSet an. Da die Anzahl der Variablen (14) es nicht erlaubt, diese in einem Chart zu platzieren, teilen wir sie in drei Gruppen auf.

require(GGally)
evalq(ggpairs(dataSet, columns = 2:6, 
              mapping = aes(color = Class),
              title = "DigFilter1"), 
      env)

digFilter 1

Abb. 6. Die erste Gruppe der Prädiktoren

evalq(ggpairs(dataSet, columns = 7:10, 
              mapping = aes(color = Class),
              title = "DigFilter2"), 
      env)

digFilter 2

Abb. 7. Die zweite Gruppe der Prädiktoren

evalq(ggpairs(dataSet, columns = 11:14, 
              mapping = aes(color = Class),
              title = "DigFilter3"), 
      env)

digFilter 3

Abb. 8. Die dritte Gruppe der Prädiktoren

Was wir in den Charts sehen:

  • alle Prädiktoren haben eine Verteilungsform, die nahe an der normalen ist, aber mit einer stark ausgeprägten Rechtsschiefe;
  • alle Prädikatoren haben einen kleinen Interquartilsabstand (IQR);
  • alle Prädiktoren haben erhebliche Ausreißer;
  • die Anzahl der Beispiele auf den zwei Ebenen der Zielvariablen "Class" unterscheidet sich ein wenig von einander.

3. Datenaufbereitung

In der Regel besteht die Datenaufbereitung aus sieben Schritten:

  • “imputation” — Entfernen oder Imputation fehlender/undefinierter Daten;
  • “variance” — Entfernen der Variablen mit der Null- oder Nahe-Null-Varianz;
  • “split” — Aufteilen des Datensatzes in train/valid/test;
  • “scaling” — Skalieren des Bereichs der Variablen;
  • “outliers” — Entfernen oder Imputation von Ausreißern;
  • “sampling” — Korrigieren des Ungleichgewichts der Klassen;
  • “denoise” — Entfernen oder Umdefinieren vom Rauschen;
  • “selection” — Selektion irrelevanter Prädiktoren.

3.1. Bereinigen von Daten

Die erste Phase der Aufbereitung von Ausgangsdaten stellt das Entfernen oder die Imputation undefinierter Werte und Lücken in den Daten dar. Obwohl viele Modelle die Verwendung undefinierter Daten (NA) und fehlender Daten in Datensätzen zulassen, ist es besser, diese vor den Manipulationen zu entfernen. Diese Operation wird für den vollen Datensatz unabhängig vom verwendeten Modell durchgeführt.

Die Gesamtstatistik für unsere Rohdaten weist darauf hin, dass NA im Datensatz vorhanden sind. Das sind künstliche A, die sich bei der Berechnung digitaler Filter gebildet haben. Ihre Anzahl ist relativ klein, deswegen kann man sie entfernen. Wir haben bereits den Datensatz dataSet erhalten, der für die weitere Verarbeitung bereit ist. Bereinigen wir die Daten.

Im Allgemeinen versteht man unter einer "Bereinigung" folgendes:

  • Entfernen von Prädiktoren mit einer Null- oder Nahe-Null-Varianz (method = c(“zv”, “nzv”));
  • Entfernen stark korrelierter Variablen. Der Grenzwert der Korrelation wird von Nutzer gesetzt (method = “corr”), standardmäßig ist er gleich 0.9. Diese Phase ist nicht immer nötig, das hängt von den weiteren Methoden der Umwandlung ab;
  • Entfernen von Prädiktoren, die nur einen einmaligen Wert in einer beliebigen Klasse haben (method = “conditionalX”).

Alle diese Operationen sind in der Funktion preProcess()::caret durch die oben angeführten Methoden implementiert und werden für den vollen Datensatz vor der Aufteilung in Einlern- und Testdatensätze durchgeführt.

require(caret)
evalq({preProClean <- preProcess(x = dataSet,method = c("zv", "nzv", "conditionalX", "corr"))
      dataSetClean <- predict(preProClean, dataSet %>% na.omit)},
env)

Schauen wir, ob es entfernte Prädikatoren gibt, und was nach dem Bereinigen übrig geblieben ist:

> env$preProClean$method$remove
#[1] "v.rbci" 
> dim(env$dataSetClean)
[1] 7906   13
> colnames(env$dataSetClean)
 [1] "Data"   "ftlm"   "stlm"   "rbci"   "pcci"  
 [6] "v.fatl" "v.satl" "v.rftl" "v.rstl" "v.ftlm"
[11] "v.stlm" "v.pcci" "Class"

3.2. Erkennung und Verarbeitung von Ausreißern (outliers)

Probleme mit der Datenqualität, solche wie Schiefe oder Ausreißer, hängen oft voneinander ab. Dies macht eine vorläufige Verarbeitung aufwändig und erschwert die Aufgabe, Zusammenhänge und Tendenzen in der Stichprobe festzustellen.

Was ist ein Ausreißer?

Definieren wir den Begriff: unter einem Ausreißer verstehen wir eine Beobachtung, die zu groß oder zu klein im Vergleich zu den meisten anderen Beobachtungen ist. Eine gründliche Klassifikation von Ausreißern sowie Methoden deren Erkennung und Verarbeitung ist in [2] angeführt.

Typen der Ausreißer

Ausreißer verzerren die Verteilung von Variablen und die anhand solcher Daten trainierten Modelle. Es gibt eine Vielzahl von Methoden für die Erkennung und Verarbeitung von Ausreißern. Sie hängen davon ab, ob wir den Ausreißer global oder lokal identifizieren. Lokale Ausreißer stellen Ausreißer einer Variablen dar. Globale Ausreißer sind Ausreißer in einem mehrdimensionalen Raum, der durch eine Matrix oder einen Datenrahmen definiert wird.

Was verursacht Ausreißer?

Alle Ausreißer kann man nach ihrem Ursprung aufteilen:

Künstliche

  • Eingabefehler — Fehler, die während des Sammelns, Schreibens oder der Verarbeitung von Daten auftreten;
  • experimentelle Fehler;
  • Stichprobenfehler.

Natürliche — Ausreißer, die durch die Natur der Variablen bedingt sind.

Welche Auswirkungen haben Ausreißer?

Die Ausreißer können die Ergebnisse der Datenanalyse oder einer statistischen Modellierung erheblich beeinträchtigen: sie erhöhen die Fehlervarianz und reduzieren die Leistung der statistischen Tests; wenn die Ausreißer nicht zufällig verteilt wurden, können sie auch die Normalität reduzieren; darüber hinaus können sie die grundlegenden Annahmen der Regressionsanalyse, Varianzanalyse und anderer statistischer Annahmen des Modells beeinflussen.

Wie kann man lokale Ausreißer erkennen?

Am häufigsten werden die Ausreißer durch das Visualisieren von Daten ermittelt. Eine einfache und breit verwendete Methode ist boxplot. Schauen wir uns zum Beispiel den Prädiktor ftlm an:

evalq(ggplot(dataSetClean, aes(x = factor(0), 
                               y = ftlm,
                               color = 'red')) + 
        geom_boxplot() + xlab("") + 
        scale_x_discrete(breaks = NULL) + 
        coord_flip(),
      env)

Outlier ftlm

Abb.9. Boxplot ftlm

Erläuterungen zum Bild:

IQR — Interquartilsabstand, oder der Abstand zwischen dem ersten und dritten Quartil.

In diesem Zusammenhang gibt es mehrere Definitionen von Ausreißern:

  • Jeder Wert, der kleiner als -1.5*IQR und größer +1.5*IQR ist, ist ein Ausreißer. Manchmal wird der Koeffizient auf 2 oder 3 gesetzt. Alle Werte zwischen 1.5*IQR und 3*IQR werden als "milde" Ausreißer bezeichnet, alle Werte, die oberhalb von 3*IQR liegen, sind extreme Ausreißer.
  • Jeder Wert außerhalb des Bereichs vom 5. und 95. Perzentil kann als Ausreißer bezeichnet werden.
  • Punkte mit einem Abstand von drei Standardabweichungen und mehr sind auch Ausreißer.

Weiterhin werden wir die erste Definition des Ausreißers verwenden (über IQR).

Wie werden Ausreißer verarbeitet?

Die meisten Methoden für die Verarbeitung von Ausreißern sind ähnlich den Methoden für die Verarbeitung undefinierter Werte NA: Entfernen, Umwandeln, Aufteilen, Imputation und andere statistische Methoden.

  • Entfernen von Ausreißern: wir entfernen die Werte von Ausreißern, wenn sie infolge eines Eingabefehlers oder eines Verarbeitungsfehlers auftreten oder wenn es zu wenige Ausreißer gibt. Darüber hinaus können wir die Verteilung an beiden Enden schneiden, um Ausreißer zu entfernen. Zum Beispiel, jeweils 1% von oben und von unten zu entfernen.

  • Transformation und Binning (Gruppieren von Werten in Intervalle):
    • eine Transformation der Variablen kann Ausreißer ausschließen (dies wird im nächsten Abschnitt des Artikels betrachtet);
    • natürlicher Logarithmus reduziert die Änderungen, die die Extremwerte verursachen (ebenso im nächsten Abschnitt betrachtet);
    • Diskretisierung ist eine weitere Art, eine Variable umzuwandeln (s. den nächsten Abschnitt);
    • wir können auch die Zuweisung von Gewichten für verschiedene Beobachtungen verwenden (dieses Thema wird nicht betrachtet).

  • Imputation: die gleichen Methoden, die wir für die Imputation undefinierter Werte verwenden, können auf Ausreißer angewandt werden. Man kann Mittelwert, Medianwert oder Modalwert verwenden. Vor der Imputation eines Wertes, müssen wir feststellen, ob wir mit einem natürlichen oder künstlichen Ausreißer zu tun haben. Wenn es um einen künstlichen Ausreißer geht, kann er imputiert werden.

Wenn es in einer Stichprobe eine erhebliche Anzahl von Ausreißern gibt, müssen wir diese separat in einem statistischen Modell betrachten. Hier werden wir nur von allgemeinen Methoden sprechen: Entfernen und Imputieren.

Entfernen von Ausreißern

Wir entfernen die Werte von Ausreißern, wenn sie infolge eines Eingabefehlers oder eines Verarbeitungsfehlers aufgetreten sind oder wenn es zu wenige Ausreißer gibt (nur beim Bestimmen statistischer Messwerte der Variablen).

Die Daten einer Variablen (z.B. ftlm) können ohne Ausreißer wie folgt extrahiert werden:

evalq({dataSetClean$ftlm -> x  
  out.ftlm <- x[!x %in% boxplot.stats(x)$out]}, 
  env)

Oder so:

evalq({dataSetClean$ftlm -> x 
  out.ftlm1 <- x[x > quantile(x, .25) - 1.5*IQR(x) & 
          x < quantile(x, .75) + 1.5*IQR(x)]},
  env) 

Sind sie gleich?

> evalq(all.equal(out.ftlm, out.ftlm1), env)
[1] TRUE

Wie viele Ausreißer sind im Datensatz?

> nrow(env$dataSetClean) - length(env$out.ftlm)
[1] 402 

Schauen wir, wie ftlm ohne Ausreißer aussieht:

boxplot(env$out.ftlm, main = "ftlm  without outliers", 
        boxwex = 0.5)

Outlier 2

Abb. 10. ftlm ohne Ausreißer

Die oben beschriebene Methode kann nicht für Matrizes und Dataframes verwendet werden, denn jede Variable in einem Dataframe kann verschiedene Anzahl von Ausreißern haben. Für solche Stichproben eignet sich die Methode der Ersetzung lokaler Ausreißer durch NA mit der weiteren Verwendung von Standardmethoden für die Verarbeitung von NA. Schreiben wir eine Funktion, die lokale Ausreißer durch NA ersetzt:

#-------remove_outliers-------------------------------
remove_outliers <- function(x, na.rm = TRUE, ...) {
  qnt <- quantile(x, probs = c(.25, .75), 
                  na.rm = na.rm, ...)
  H <- 1.5 * IQR(x, na.rm = na.rm)
  y <- x
  y[x < (qnt[1] - H)] <- NA
  y[x > (qnt[2] + H)] <- NA
  y
}

In unserem Datensatz dataSetClean ersetzen wir Ausreißer durch NA in allen Variablen außer c(Data, Class) und schauen, wie sich die Verteilung des erhaltenen Satzes x.out ändert:

evalq({
  dataSetClean %>% select(-c(Data,Class)) %>% as.data.frame() -> x 
  foreach(i = 1:ncol(x), .combine = "cbind") %do% {
    remove_outliers(x[ ,i])
  } -> x.out
  colnames(x.out) <- colnames(x)
  },  
env)
par(mfrow = c(1, 1))
chart.Boxplot(env$x, 
              main = "x.out with outliers",
              xlab = "")

Outlier 3

Abb. 11. Daten mit Ausreißern

chart.Boxplot(env$x.out, 
              main = "x.out without outliers",
              xlab = "")

Outlier 4

Abb.12. Daten ohne Ausreißer

Imputation von NA, die die Ausreißer ersetzten

Unter Imputation versteht man die Ersetzung fehlender, falscher oder ungültiger Werte durch andere Werte. Die Input-Daten für das Trainieren des Modells dürfen nur gültige Werte beinhalten. Mögliche Varianten:

  • wir ersetzen NA durch mean, mediana, mod (dabei ändern sich die statistischen Charakteristiken des Datensatzes nicht)
  • wir ersetzen die Ausreißer, die größer als 1.5*IQR sind, durch 0.95 Perzentil, und die Ausreißer, die kleiner als - 1.5*IQR sind, durch 0.05 Perzentil.

Schreiben wir eine Funktion, die die letzte Variante umsetzt, und schauen wir uns die Verteilung nach der Umwandlung an:

#-------capping_outliers-------------------------------
capping_outliers <- function(x, na.rm = TRUE, ...) {
  qnt <- quantile(x, probs = c(.25, .75), 
                  na.rm = na.rm, ...)
  caps <- quantile(x, probs = c(.05, .95), 
                   na.rm = na.rm, ...)
  H <- 1.5 * IQR(x, na.rm = na.rm)
  y <- x
  y[x < (qnt[1] - H)] <- caps[1] 
  y[x > (qnt[2] + H)] <- caps[2] 
  y
}

evalq({dataSetClean %>% select(-c(Data,Class)) %>%
    as.data.frame() -> x 
    foreach(i = 1:ncol(x), .combine = "cbind") %do% {
      capping_outliers(x[ ,i])
    } -> x.cap
    colnames(x.cap) <- colnames(x)
   },  
env)
chart.Boxplot(env$x.cap, 
              main = "x.cap with capping outliers",
              xlab = "")



Outlier 5

Abb.13. Datensatz mit imputierten Ausreißern

Schauen wir auf die Variation und die Kovariation unserer Variablen im Datensatz dataSetCap (das ist der gleiche Datensatz dataSet, aber bereinigt und mit imputierten lokalen Ausreißern). Da die Anzahl der Variablen (13) es nicht erlaubt, diese in einem Chart zu platzieren, teilen wir diese in drei Gruppen auf.

evalq(x.cap %>% tbl_df() %>% 
        cbind(Data = dataSetClean$Data, .,
              Class = dataSetClean$Class) -> 
        dataSetCap, 
      env)
require(GGally)
evalq(ggpairs(dataSetCap, columns = 2:7, 

              mapping = aes(color = Class),
              title = "PredCap1"), 
      env)

Outlier 6


Abb.14. Variation und Kovariation des ersten Teils des Datensatzes mit imputierten Ausreißern.

 Der zweite Teil des Datensatzes:

evalq(ggpairs(dataSetCap, columns = 8:13, 
              mapping = aes(color = Class),
              title = "PredCap2"), 
      env)

Outlier 7

Abb.15. Variation und Kovariation des zweiten Teils des Datensatzes mit imputierten Ausreißern

Wie kann man globale Ausreißer erkennen?

Zwei- und mehrdimensionale Ausreißer werden gewöhnlich mithilfe des Einfluss- oder Distanz-Index. Um globale Ausreißer festzustellen, werden verschiedene Abstände verwendet. Wir können dafür mehrere Pakete, z.B. DMwR, mvoutliers, Rlof, verwenden. Globale Ausreißer werden mithilfe von LOF (local outlier factor) bewertet. Berechnen und vergleichen wir LOF für den Datensatz mit Ausreißern x und für den Datensatz mit imputierten Ausreißern x.cap.

##------DMwR2-------------------
require(DMwR2)
evalq(lof.x <- lofactor(x,10), env)
evalq(lof.x.cap <- lofactor(x.cap,10), env)
par(mfrow = c(1, 3))
boxplot(env$lof.x, main = "lof.x", 
        boxwex = 0.5)
boxplot(env$lof.x.cap, main = "lof.x.cap", 
        boxwex = 0.5)
hist(env$lof.x.cap, breaks = 20)
par(mfrow = c(1, 1))

LOF 1

Abb.16. Globaler Ausreißerfaktor für einen Datensatz mit Ausreißern und einen Datensatz mit imputierten Ausreißern

Im Paket Rlof ist die Funktion lof() impelentiert. Sie findet den lokalen Faktor outlier [3] der Daten unter Verwendung von k Nachbarn. Lokaler Ausreißerfaktor (LOF) ist ein Wahrscheinlichkeitswert der Zugehörigkeit zu Ausreißern und wird für jede Beobachtung separat berechnet. Basierend auf diesem Wert trifft der Nutzer die Entscheidung, ob eine Beobachtung als Ausreißer betrachtet wird oder nicht.

Der LOF berücksichtigt die lokale Dichte, um festzustellen, ob eine Beobachtung ein Ausreißer ist. Das ist eine effizientere Implementierung des LOF unter Verwendung einer anderen Datenstruktur und Funktion zur Berechnung des Abstands im Vergleich zur Funktion lofactor(), die im Paket “dprep” verfügbar ist. Er unterstützt mehrere k Werte, die parallel berechnet werden, sowie verschiedene Abstandswerte, außer dem Euklidischen Abstand. Die Berechnungen werden parallel auf mehreren Kernen des Prozessors durchgeführt. Berechnen wir lofactor für dieselben Datensätze (x und x.cap) für 5, 6, 7, 8, 9 und 10 Nachbarn unter Verwendung der Minkowski Methode für die Berechnung des Abstands. Zeichnen wir die Histogramme dieser lofactor.

require(Rlof)
evalq(Rlof.x <- lof(x, c(5:10), cores = 2,
                       method = 'minkowski'),
        env)
  evalq(Rlof.x.cap <- lof(x.cap, c(5:10), 
                          cores = 2, 
                          method = 'minkowski'),
        env)
par(mfrow = c(2, 3))  
hist(env$Rlof.x.cap[ ,6], breaks = 20)
hist(env$Rlof.x.cap[ ,5], breaks = 20)
hist(env$Rlof.x.cap[ ,4], breaks = 20)
hist(env$Rlof.x.cap[ ,3], breaks = 20)
hist(env$Rlof.x.cap[ ,2], breaks = 20)
hist(env$Rlof.x.cap[ ,1], breaks = 20)
par(mfrow = c(1, 1))

 

LOF 2

Abb.17. Globaler Ausreißerfaktor für k Nachbarn

 Fast alle Punkte liegen im Bereich lofactor =1.6. Außerhalb des Bereichs:

> sum(env$Rlof.x.cap[ ,6] >= 1.6)
[1] 32

Die unbedeutende Anzahl der Ausreißer ist akzeptabel für diese Datensatzgröße.

ANMERKUNG. Um die Bereichsgrenzen zu definieren, beim Überschreiten deren die Werte der Variablen als Ausreißer bezeichnet werden, muss man einen Trainigsdatensatz verwenden. Die Werte der Variablen des Test-/Validierungsdatensatzes werden mit den Parametern verarbeitet, die auf dem Trainigsdatensatz erhalten wurden. Welche Parameter sind das? Die in den vorherigen Berechnungen verwendeten Grenzen — upper = 1.5*IQR, lower = -1.5*IQR und cap =c(0.05, 0.95) percentil. Wenn andere Methoden für die Berechnung der Bereichsgrenzen und der Imputation von Ausreißern festgelegt wurden, muss man diese für den Trainigsdatensatz definieren, speichern und später bei der Verarbeitung der Datensätze valid/test verwenden.

Schreiben wir eine Funktion, die Zwischenberechnungen durchführt:

#-----prep.outlier--------------
prep.outlier <- function(x, na.rm = TRUE, ...) {
  qnt <- quantile(x, probs = c(.25, .75), 
                  na.rm = na.rm, ...)
  H <- 1.5 * IQR(x, na.rm = na.rm)
  caps <- quantile(x, probs = c(.05, .95), 
                   na.rm = na.rm, ...)
  list(lower = qnt[1] - H, upper = qnt[2] + H, 
       med = median(x), 
       cap1 = caps[1], cap2 = caps[2])
}

Berechnen wir die Parameter, die für die Erkennung und Imputation von Ausreißern notwendig sind. Legen wir die vorläufige Länge des Trainingsdatensatzes als 4000 Balken am Anfang fest und die weiteren 2000 Balken werden wir als Testdatensatz verwenden.

evalq(
  {train <- x[1:4000, ] 
  foreach(i = 1:ncol(train), .combine = "cbind") %do% {
    prep.outlier(train[ ,i]) %>% unlist()
  } -> pre.outl
  colnames(pre.outl) <- colnames(x)
  #pre.outl %<>% t()
  },  
  env)

Schauen wir uns das Ergebnis an:

> env$pre.outl
                   ftlm        stlm         rbci          pcci
lower.25% -0.2224942912 -0.59629203 -0.253231002 -9.902232e-02
upper.75%  0.2214486206  0.59242529  0.253529797  9.826936e-02
med       -0.0001534451  0.00282525 -0.001184966  8.417127e-05
cap1.5%   -0.1700418145 -0.40370452 -0.181326658 -6.892085e-02
cap2.95%   0.1676526431  0.39842675  0.183671973  6.853935e-02
                 v.fatl        v.satl        v.rftl        v.rstl
lower.25% -0.0900973332 -4.259328e-02 -0.0558921804 -0.2858430788
upper.75%  0.0888110249  4.178418e-02  0.0555115004  0.2889057397
med       -0.0008581219 -2.130064e-05 -0.0001707447 -0.0001721546
cap1.5%   -0.0658731640 -2.929586e-02 -0.0427927888 -0.1951978435
cap2.95%   0.0662353821  3.089833e-02  0.0411091859  0.1820803387
                 v.ftlm        v.stlm        v.pcci
lower.25% -0.1115823754 -5.366875e-02 -0.1115905239
upper.75%  0.1108670403  5.367466e-02  0.1119495436
med       -0.0003560178 -6.370034e-05 -0.0003173464
cap1.5%   -0.0765431363 -3.686945e-02 -0.0765950814
cap2.95%   0.0789209957  3.614423e-02  0.0770439553

Wie wir sehen, werden für jede Variable im Datensatz das erste und das dritte Quartil und der Medianwert sowie das 5. und 95. Perzentil definiert. Das ist alles, was man für die Erkennung und Verarbeitung von Ausreißern benötigt.

Wir brauchen eine Funktion, die die Ausreißer jeden Datensatzes nach vordefinierten Parametern verarbeiten wird. Mögliche Varianten der Verarbeitung: Ersetzen von Ausreißern durch NA,Ersetzen von Ausreißern durch den Medianwert, Ersetzen von Ausreißern durch 5./95. Perzentil.

#---------treatOutlier---------------------------------
  treatOutlier <- function(x, impute = TRUE, fill = FALSE,
                         lower, upper, med, cap1, cap2){ 
  if (impute) {
    x[x < lower] <- cap1 
    x[x > upper] <- cap2 
    return(x)
  }
  if (!fill) {
    x[x < lower | x > upper] <- NA 
    return(x)  
  } else {
    x[x < lower | x > upper] <- med
    return(x)
  }
} 

Da wir die notwendigen Parameter bereits im Trainigsdatensatz definiert haben, verarbeiten wir die Ausreißer im Trainingsdatensatz, indem wir diese durch das 5./95. Perzentil ersetzen. Danach verarbeiten wir auf die gleiche Weise Ausreißer im Testdatensatz und vergleichen die Verteilungen in den erhaltenen Datensätzen, indem wir drei Charts zeichnen.

#------------
evalq(
  {
  foreach(i = 1:ncol(train), .combine = 'cbind') %do% {
    stopifnot(exists("pre.outl", envir = env))
    lower = pre.outl['lower.25%', i] 
    upper = pre.outl['upper.75%', i]
    med = pre.outl['med', i]
    cap1 = pre.outl['cap1.5%', i] 
    cap2 = pre.outl['cap2.95%', i] 
    treatOutlier(x = train[ ,i], impute = T, fill = T, lower = lower,
                 upper = upper, med = med, cap1 = cap1, cap2 = cap2) 
  } -> train.out
  colnames(train.out) <- colnames(train)
  },
  env
) 
#-------------
evalq(
  {test <- x[4001:6000, ] 
  foreach(i = 1:ncol(test), .combine = 'cbind') %do% {
    stopifnot(exists("pre.outl", envir = env))
    lower = pre.outl['lower.25%', i] 
    upper = pre.outl['upper.75%', i]
    med = pre.outl['med', i]
    cap1 = pre.outl['cap1.5%', i] 
    cap2 = pre.outl['cap2.95%', i] 
    treatOutlier(x = test[ ,i], impute = T, fill = T, lower = lower,
                 upper = upper, med = med, cap1 = cap1, cap2 = cap2) 
  } -> test.out
  colnames(test.out) <- colnames(test)
  },
  env
)
#---------------
evalq(boxplot(train, main = "train  with outliers"), env)
evalq(boxplot(train.out, main = "train.out  without outliers"), env)
evalq(boxplot(test.out, main = "test.out  without outliers"), env)
#------------------------

Outlier 8

Abb.18. Trainingsdatensatz mit Ausreißern

Outlier 9

Abb.19. Trainingsdatensatz mit imputierten Ausreißern

Outlier 10

Abb.20. Testdatensatz mit imputierten Ausreißern

Nicht alle Modelle sind empfindlich gegenüber Ausreißern. Unempfindlich sind zum Beispiel die Modelle der "Entscheidungsbäume"(DT) und der "Random Forest" (RF). 

Beim Erkennen und Verarbeiten von Ausreißern können auch weitere Pakete hilfreich sein, dazu gehören “univOutl”, “mvoutlier”, “outlier”, funModeling::prep.outlier().

3.3. Beseitigen der Schiefe (skewness)


Die Schiefe gibt die Form der Verteilung an. Die allgemeine Methode deren Überprüfung besteht darin, den Koeffizienten der Schiefe einer Variablen zu berechnen. In der Regel zeugt eine negative Schiefe davon, dass der Mittelwert kleiner als der Medianwert ist, und die Verteilung linksschief ist. Eine positive Schiefe weist darauf hin, dass der Mittelwert größer als der Medianwert ist, und die Verteilung rechtsschief ist.

Wenn die Schiefe der Prädiktoren gleich 0 ist, geht es um eine symmetrische Verteilung.
Wenn die Schiefe des Prädiktors kleiner als -1 oder größer als +1 ist, sind die Daten stark verzerrt.
Wenn die Schiefe des Prädiktors zwischen -1 und -1/2 oder zwischen +1 und +1/2 liegt, sind die Daten mäßig verzerrt.
Wenn die Schiefe des Prädiktors gleich -1/2 und +1/2 ist, sind die Daten ungefähr symmetrisch.

Die Rechtsschiefe wird durch Logarithmieren, die Linkschiefe — durch Exponieren beseitigt.

Wir haben bereits davon gesprochen, dass Schiefe, Ausreißer und andere Transformationen miteinander verbunden sind. Schauen wir, wie sich der Wert der Schiefe nach dem Entfernen und der Imputation von Ausreißern änderte.  

evalq({
  sk <- skewness(x) 
  sk.out <- skewness(x.out) 
  sk.cap <- skewness(x.cap)
  }, 
  env)
> env$sk
             ftlm     stlm     rbci     pcci   v.fatl
Skewness 4.219857 1.785286 2.304655 6.491546 5.274871
           v.satl   v.rftl   v.rstl   v.ftlm    v.stlm
Skewness 2.677162 3.954098 1.568675 1.207227 0.8516043
           v.pcci
Skewness 3.031012
> env$sk.out
                ftlm        stlm        rbci       pcci
Skewness -0.04272076 -0.07893945 -0.02460354 0.01485785
             v.fatl      v.satl      v.rftl      v.rstl
Skewness 0.00780424 -0.02640635 -0.04663711 -0.04290957
                v.ftlm     v.stlm       v.pcci
Skewness -0.0009597876 0.01997082 0.0007462494
> env$sk.cap
                ftlm        stlm        rbci       pcci
Skewness -0.03329392 -0.07911245 -0.02847851 0.01915228
             v.fatl      v.satl      v.rftl      v.rstl
Skewness 0.01412182 -0.02617518 -0.03412228 -0.04596505
              v.ftlm      v.stlm      v.pcci
Skewness 0.008181183 0.009661169 0.002252508

Der Datensatz mit entfernten Ausreißern x.out sowie der mit imputierten Ausreißern x.cap sind ideal symmetrisch und brauchen nicht korrigiert zu werden.

Betrachten wir auch den Koeffizienten der Kurtosis. Die Kurtosis ist eine Maßzahl der Steilheit einer Wahrscheinlichkeitsfunktion. Bei einer normalen Verteilung ist der Koeffizient gleich Null. Er ist positiv, wenn die Spitze der Verteilung um den Erwartungswert steil ist und negativ bei einer flachen Wölbung.

require(PerformanceAnalytics)
evalq({
  k <- kurtosis(x) 
  k.out <- kurtosis(x.out) 
  k.cap <- kurtosis(x.cap)
}, 
env)
> env$k
                    ftlm     stlm     rbci     pcci
Excess Kurtosis 84.61177 16.77141 45.01858 247.9795
                  v.fatl   v.satl  v.rftl   v.rstl
Excess Kurtosis 145.9547 36.99944 74.4307 13.57613
                  v.ftlm   v.stlm   v.pcci
Excess Kurtosis 86.36448 23.06635 233.5408
> env$k.out
                        ftlm       stlm       rbci
Excess Kurtosis -0.003083449 -0.1668102 -0.1197043
                       pcci      v.fatl      v.satl
Excess Kurtosis -0.05113439 -0.02738558 -0.04341552
                     v.rftl     v.rstl     v.ftlm
Excess Kurtosis -0.01219999 -0.1316499 -0.0287925
                    v.stlm      v.pcci
Excess Kurtosis -0.1530424 -0.09950709
> env$k.cap
                      ftlm       stlm       rbci
Excess Kurtosis -0.2314336 -0.3075185 -0.2982044
                      pcci     v.fatl     v.satl
Excess Kurtosis -0.2452504 -0.2389486 -0.2331203
                    v.rftl     v.rstl     v.ftlm
Excess Kurtosis -0.2438431 -0.2673441 -0.2180059
                    v.stlm     v.pcci
Excess Kurtosis -0.2763058 -0.2698028

Im Ausgangsdatensatz x sind die Spitzen der Verteilung der Variablen sehr steil (Kurtosis viel größer als 0). Im Datensatz x.out mit entfernten Ausreißern sind die Hochs nahe an einer "normalen" Steilheit, im Datensatz mit imputierten Ausreißern sind die Wölbungen flacher. Beide Datensätze erfordern keine Korrekturen.

Anhang

1. Die Zip-Datei DARCH12_1.zip beinhaltet Skripts zum ersten Teil des Artikels (dataRaw.R, PrepareData.R, FUNCTION.R) sowie das Bild der Sitzung Rstudio mit Quelldaten von Cotir.RData. Laden Sie diese in Rstudio und sie können alle Skripts laufen lassen und mit ihnen experimentieren. Sie können das von Git /Part_I herunterlaaden.

2. Die Zip-Datei ACTF.zip beinhaltet den Artikel "New Adaptive Method of Following the Tendency and Market Cycles" von V. Kravchuk

3. Die Zip-Datei R_intro.zip beinhaltet Fachliteratur zur R.

[1] A Systematic Approach on Data Pre-processing In Data Mining. COMPUSOFT, An international journal of advanced computer technology, 2 (11), November-2013 (Volume-II, Issue-XI)

[2] Outlier Detection Techniques.Hans-Peter Kriegel, Peer Kröger, Arthur Zimek. Ludwig-Maximilians-Universität München.Munich, Germany

[3] Breuning, M., Kriegel, H., Ng, R.T, and Sander. J. (2000). LOF: Identifying density-based local outliers. In Proceedings of the ACM SIGMOD International Conference on Management of Data.