English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Explorative Datenanalyse in Python (Funktional)

Hier sind einige Tricks, um Logdatei-Extrakte zu behandeln. Angenommen, wir betrachten einige Enterprise Splunk-Extrakte. Wir können Daten mit Splunk erkunden oder eine einfache Extraktion erhalten und diese Daten in Python bearbeiten.

Es scheint effektiver zu sein, verschiedene Experimente in Python durchzuführen, als solche explorativen Operationen in Splunk zu versuchen. Das liegt daran, dass wir mit den Daten ohne Einschränkungen tun können, was wir möchten. Wir können sehr komplexe statistische Modelle an einem Ort erstellen.

Theoretisch können wir in Splunk eine Vielzahl von Erkundungen durchführen. Es verfügt über verschiedene Berichts- und Analysefunktionen.

Aber...

Um Splunk zu verwenden, müssen wir annehmen, dass wir wissen, wonach wir suchen. Oft wissen wir nicht, wonach wir suchen: wir erkunden. Es könnten einige Anzeichen dafür geben, dass einige RESTful API langsam verarbeitet werden, aber das ist nicht alles. Wie gehen wir weiter?

Der erste Schritt ist, die ursprünglichen CSV-formatierten Daten zu erhalten. Wie geht das?

Lesen Sie die ursprünglichen Daten

Wir werden zunächst einige zusätzliche Funktionen verwenden, um ein CSV.DictReader-Objekt zu verpacken.

Objektorientierte Puristen würden diese Strategie ablehnen. „Warum nicht DictReader erweitern?“ fragen sie. Ich habe keine gute Antwort. Ich neige zu funktionaler Programmierung und der Orthogonalität von Komponenten. Für eine rein objektorientierte Methode müssten wir eine komplexere Hybridlösung verwenden.

Unser allgemeiner Rahmen zur Verarbeitung von Protokollen ist wie folgt.

with open("somefile.csv") as source:
rdr = csv.DictReader(source)

Das ermöglicht es uns, CSV-formatierte Splunk-Extrakte zu lesen. Wir können die Zeilen im Leser iterieren. Das ist der Trick #1. Das ist nicht besonders knifflig, aber ich mag es.

with open("somefile.csv") as source:
rdr = csv.DictReader(source)
for row in rdr:
print("{host} {ResponseTime} {source} {Service}".format_map(row))

können wir - In gewisser Weise - Berichten Sie die ursprünglichen Daten in einem nützlichen Format. Wenn wir die Ausgabe aufhübschen möchten, können wir den Formatierungsstring ändern. Das könnte sein: „{Host:30s} {Antwortzeit:8s} {Quelle: s} oder ähnliches.

Filterung

Oft extrahieren wir zu viel, aber wir müssen tatsächlich nur einen Teilbereich betrachten. Wir können den Splunk-Filter ändern, aber es ist lästig, ihn vor unserer Erkundung übermäßig zu verwenden. Die Filterung in Python ist viel einfacher. Sobald wir wissen, was wir benötigen, können wir es in Splunk erledigen.

with open("somefile.csv") as source:
rdr = csv.DictReader(source)
rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log')
for row in rdr_perf_log:
print( "{host} {ResponseTime} {Service}".format_map(row) )

Wir haben einen Generator-Ausdruck hinzugefügt, um die Quelldatenzeilen zu filtern, der einen sinnvollen Teilbereich verarbeiten kann.

Projektion

In einigen Fällen fügen wir zusätzliche Quelldaten-Spalten hinzu, die wir nicht verwenden möchten. Daher werden wir diese Daten durch Projektion für jede Zeile entfernen.

Prinzipiell erzeugt Splunk niemals leere Spalten. Aber RESTful API-Protokolle können dazu führen, dass viele Spaltenüberschriften in den Datensätzen enthalten sind, die auf einem Teil der Anfrage-URI basierenden Proxy-Schlüsseln entsprechen. Diese Spalten enthalten eine Zeile Daten aus einer Anfrage, die diesen Proxy-Schlüssel verwendet. Für andere Zeilen ist diese Spalte nutzlos. Daher müssen diese leeren Spalten entfernt werden.

Wir könnten das auch mit einem Generator-Expression tun, aber es würde etwas länger werden. Die Generator-Funktion ist einfacher lesbar.

def project(reader):
for row in reader:
yield {k:v for k,v in row.items() if v}

Wir haben aus einem Teil des ursprünglichen Lesers einen neuen Zeilen-Dictionary gebaut. Wir können es verwenden, um die Ausgabe unseres Filters zu verpacken.

with open("somefile.csv") as source:
rdr = csv.DictReader(source)
rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log')
for row in project(rdr_perf_log):
print( "{host} {ResponseTime} {Service}".format_map(row) )

Das wird die Anzahl der nicht verwendeten Spalten im for-Befehl reduzieren.

Symboländerung

Das Symbol row['source'] wird etwas umständlicher. types.SimpleNamespace ist besser als ein Dictionary zu verwenden. Dies ermöglicht es uns, row.source zu verwenden.

Das ist ein cooler Trick, um nützlichere Dinge zu schaffen.

rdr_ns= (types.SimpleNamespace(**row) forrowinreader)

Wir können es in eine solche Sequenz von Schritten zusammenfassen.

with open("somefile.csv") as source:
rdr = csv.DictReader(source)
rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log')
rdr_proj = project(rdr_perf_log)
rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj)
for row in rdr_ns:
print( "{host} {ResponseTime} {Service}".format_map(vars(row)) )

Bitte beachten Sie die kleine Änderung an der Methode format_map(). Von den Attributen von SimpleNamespace hinzugefügt haben wir die Funktion vars() zur Extraktion des Dictionaries.

Wir können es mit anderen Funktionen in eine Funktion umwandeln, um syntaktische Symmetrie zu bewahren.

def ns_reader(reader):
return (types.SimpleNamespace(**row) for row in reader)

Tatsächlich können wir es in eine wie eine Funktion nutzbare lambda-Struktur schreiben

ns_reader = lambda reader: (types.SimpleNamespace(**row) for row in reader)

Obwohl die Verwendung von ns_reader() -Funktion und ns_reader() lambda ähnlich ist, ist es etwas schwieriger, Dokumentationsstrings und doctest-Unit-Tests für lambda zu schreiben. Aus diesem Grund sollte die Verwendung von Lambda-Strukturen vermieden werden.

Wir können map(lambda row: types.SimpleNamespace(** row), reader()). Manche mögen diese Generator-Expression.

Wir können mit einem angemessenen for-Statement und einem internen yield-Statement, aber es scheint keinen Vorteil zu haben, große Anweisungen aus kleinen Dingen zu schreiben.

Wir haben viele Möglichkeiten, weil Python so viele funktionale Programmierfunktionen bereitstellt. Obwohl wir Python nicht oft als ein funktionales Sprach verstanden haben. Aber wir haben verschiedene Methoden, um einfache Mapping zu behandeln.

Mapping: Konvertierung und Ableitung von Daten

Wir haben oft eine sehr eindeutige Liste von Datenkonvertierungen. Darüber hinaus haben wir eine Liste wachsender abgeleiteter Datenprojekte. Abgeleitete Projekte werden dynamisch und basierend auf den verschiedenen Hypothesen, die wir testen, sein. Jedes Mal, wenn wir ein Experiment oder ein Problem haben, können wir die abgeleiteten Daten ändern.

Jeder dieser Schritte: Filtern, Projektieren, Konvertieren und Ableiten sind map-Die Stufe der "map"-Teil des reduce-Pipelines. Wir können kleinere Funktionen erstellen und sie auf map() anwenden. Da wir einen objektzustandsbehafteten Objekt aktualisieren, können wir nicht die allgemeine map() -Funktion verwenden. Wenn wir eine reinere funktionalistische Programmierstil implementieren möchten, verwenden wir einen unveränderlichen namedtuple anstelle eines veränderlichen SimpleNamespace.

def convert(reader):
for row in reader:
row._time = datetime.datetime.strptime(row.Time, "%Y"-%m-%dT%H:%M:%S.%F%Z")
row.response_time = float(row.ResponseTime)
yield row

Während unseres Erkundungsvorgangs werden wir den Hauptteil dieser Konvertierungsfunktion anpassen. Vielleicht beginnen wir mit einigen kleinen Konvertierungen und Ableitungen. Wir werden mit einigen "Sind das richtige?"-Fragen die Erkundung fortsetzen. Wenn wir etwas finden, das nicht funktioniert, ziehen wir es heraus.

Unser gesamter Verarbeitungsvorgang ist wie folgt dargestellt:

with open("somefile.csv") as source:
rdr = csv.DictReader(source)
rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log')
rdr_proj = project(rdr_perf_log)
rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj)
rdr_converted = convert(rdr_ns)
for row in rdr_converted:
row.start_time = row._time - datetime.timedelta(seconds=row.response_time)
row.service = some_mapping(row.Service)
print( "{host:30s} {start_time:%H:%M:%S} {response_time:6.3f} {service}".format_map(vars(row)) )

Bitte beachten Sie die Veränderung des Hauptteils des Satzes. Die Funktion convert() erzeugt die von uns bestätigten Werte. Wir haben im for-Schleif einige zusätzliche Variablen hinzugefügt und können nicht100% sicher. Bevor wir die Funktion convert() aktualisieren, schauen wir nach, ob sie nützlich sind (oder sogar korrekt).

Reduktion

Bezüglich der Reduktion können wir eine etwas andere Verarbeitungsmethode anwenden. Wir müssen unser vorheriges Beispiel refaktorieren und es in eine Generatorfunktion umwandeln.

def converted_log(some_file):
with open(some_file) as source:
rdr = csv.DictReader(source)
rdr_perf_log = (row for row in rdr if row['source'] == 'perf_log')
rdr_proj = project(rdr_perf_log)
rdr_ns = (types.SimpleNamespace(**row) for row in rdr_proj)
rdr_converted = convert(rdr_ns)
for row in rdr_converted:
row.start_time = row._time - datetime.timedelta(seconds=row.response_time)
row.service = some_mapping(row.Service)
yield row

Dann wurde ein yield anstelle von print() verwendet.

Dies ist ein weiterer Teil der Refaktorierung.

for row in converted_log("somefile.csv"):
print( "{host:30s} {start_time:%H:%M:%S} {response_time:6.3f} {service}".format_map(vars(row)) )

Im Idealfall ist all unser Programmieren so. Wir verwenden Generatorfunktionen, um Daten zu generieren. Die endgültige Anzeige bleibt vollständig getrennt. Dies ermöglicht es uns, freier umzustrukturieren und zu ändern.

Jetzt können wir einige Dinge tun, z.B. Zeilen in ein Counter-Objekt zu sammeln oder möglicherweise einige Statistiken zu berechnen. Wir können defaultdict(list) verwenden, um Zeilen nach Diensten zu gruppieren.

by_service = defaultdict(list)
for row in converted_log("somefile.csv"):
by_service[row.service] = row.response_time
for svc in sorted(by_service):
m = statistics.mean(by_service[svc])
print( "{svc:15s} {m:.2f"}".format_map(vars()) )

我们决定在这里创建具体的列表对象。我们可以使用itertools按服务分组响应时间。它看起来像是正确的函数式编程,但是这种实施在Pythonic函数式编程形式中指出了一些限制。要么我们必须对数据进行排序(创建列表对象),要么在分组数据时创建列表。为了做好几个不同的统计,通过创建具体的列表来分组数据通常更容易。

我们现在正在做两件事情,而不是简单地打印行对象。

创建一些局部变量,如svc和m。我们可以很容易地添加变化或其他措施。

使用没有参数的vars()函数,它会从局部变量中创建一个字典。

这个使用vars()而没有参数的行为就像locals()一样是一个方便的技巧。它允许我们简单地创建我们想要的任何局部变量,并将它们包含在格式化输出中。我们可以侵入我们认为可能相关的各种统计方法中。

既然我们的基本处理循环是针对converted_log(“somefile.csv”)中的行,我们可以通过一个小巧的、易于修改的脚本探索许多处理选择。我们可以探索一些假设来确定为什么某些RESTful API处理速度慢,而其他处理速度则很快。

总结

以上所述是小编给大家介绍的Python中的探索性数据分析(功能式),希望对大家有所帮助。如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对呐喊教程的支持!

声明:本文内容来源于网络,版权归原作者所有。内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:notice#oldtoolbag.com(在发邮件时,请将#更换为@进行举报,并提供相关证据。一经查实,本站将立即删除涉嫌侵权内容。)

Gefällt mir