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

Analyse exploratoire des données en Python (fonctionnel)

Voici quelques astuces pour traiter les extractions de fichiers journaux. Supposons que nous regardions des extractions Enterprise Splunk. Nous pouvons explorer les données avec Splunk. Ou nous pouvons obtenir une extraction simple et manipuler ces données en Python.

Il semble plus efficace d'exécuter des expériences différentes en Python plutôt que de tenter des opérations exploratoires dans Splunk. Principalement parce que nous pouvons faire tout ce que nous voulons avec les données. Nous pouvons créer des modèles statistiques complexes en un seul endroit.

Théoriquement, nous pouvons faire beaucoup d'exploration dans Splunk. Il a diverses fonctionnalités de rapport et d'analyse.

Mais...

L'utilisation de Splunk suppose que nous savons ce que nous cherchons. Souvent, nous ne savons pas ce que nous cherchons : nous explorons. Il pourrait y avoir des signes que certains API RESTful sont lent, mais ce n'est pas tout. Comment continuons-nous ?

La première étape consiste à obtenir les données originales au format CSV. Comment faire ?

Lecture des données originales

Nous allons d'abord envelopper un objet CSV.DictReader avec quelques fonctions supplémentaires.

Les puristes orientés objet pourraient s'opposer à cette stratégie. “Pourquoi ne pas étendre DictReader ?” demandent-ils. Je n'ai pas de bonne réponse. J'opte pour la programmation fonctionnelle et l'orthogonalité des composants. Pour une méthode purement orientée objet, nous devrions utiliser des mélanges plus complexes pour cela.

Le cadre général que nous utilisons pour traiter les journaux est le suivant.

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

Cela nous permet de lire des extractions Splunk au format CSV. Nous pouvons itérer sur les lignes du lecteur. Voici le truc #1Ce n'est pas très difficile, mais j'aime ça.

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

Nous pouvons - Dans une certaine mesure - Rapporter les données originales dans un format utile. Si nous voulons embellir la sortie, nous pouvons changer la chaîne de format. Cela pourrait être "{Hôte : "30s} {Temps de réponse :8s} {Origine : s} ou quelque chose de similaire.

Filtrage

Dans la plupart des cas, nous extrayons trop, mais nous n'avons besoin que d'un sous-ensemble. Nous pouvons modifier le filtre Splunk, mais, avant de terminer notre exploration, l'utilisation excessive des filtres est ennuyeuse. Il est beaucoup plus facile de filtrer en Python. Une fois que nous savons ce dont nous avons besoin, nous pouvons le faire dans Splunk.

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

Nous avons ajouté une expression génératrice pour filtrer les lignes source, capable de traiter un sous-ensemble significatif.

Projet

Dans certains cas, nous ajoutons des colonnes de données sources supplémentaires que nous ne voulons pas utiliser. Alors, nous supprimons ces données en projetant chaque ligne.

En principe, Splunk ne génère pas de colonnes vides. Cependant, les journaux RESTful API peuvent entraîner la présence de nombreuses colonnes d'en-tête dans les ensembles de données, basées sur une partie de l'URI de la requête proxy. Ces colonnes contiendront les données d'une ligne pour une requête utilisant cette clé proxy. Pour les autres lignes, il n'y a rien d'utile dans cette colonne. Alors, supprimez ces colonnes vides.

Nous pouvons également le faire avec une expression génératrice, mais cela deviendra un peu long. La fonction génératrice est plus facile à lire.

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

Nous avons construit une nouvelle ligne de dictionnaire à partir d'une partie du lecteur original. Nous pouvons l'utiliser pour envelopper la sortie de nos filtres.

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

Cela réduira le nombre de colonnes non utilisées visibles dans la boucle for.

Changement de symbole

Le symbole row['source'] deviendra plus lourd. Utiliser types.SimpleNamespace est préférable à utiliser un dictionnaire. Cela nous permet d'utiliser row.source.

C'est une astuce très cool pour créer quelque chose de plus utile.

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

Nous pouvons le réduire à une séquence d'étapes comme celle-ci.

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

Veuillez noter la petite modification apportée à la méthode format_map(). À partir des attributs de SimpleNamespace, nous avons ajouté la fonction vars() pour extraire le dictionnaire.

Nous pouvons l'écrire sous forme de fonction en utilisant d'autres fonctions pour maintenir la symétrie syntaxique.

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

En effet, nous pouvons l'écrire sous forme d'une structure lambda utilisable comme une fonction

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

Although the use of the ns_reader() function and ns_reader() lambda is the same, writing documentation strings and doctest unit tests for lambda is slightly more difficult. For this reason, it is better to avoid using lambda structures.

We can use map(lambda row: types.SimpleNamespace(** row), reader()). Some people like this generator expression.

We can use an appropriate for statement and an internal yield statement, but writing large statements from a small thing seems to have no benefit.

We have many choices because Python provides so many functional programming features. Although we do not often consider Python as a functional language. But we have many ways to handle simple mappings.

Mapping: Transforming and Deriving Data

We often have a very obvious list of data transformations. In addition, we will have a list of derived data items that are increasing. Derived items will be dynamic and based on the different assumptions we are testing. Whenever we have an experiment or a problem, we may change the derived data.

Each of these steps: filtering, projecting, transforming, and deriving are map-The stage of the "map" part of the reduce pipeline. We can create some smaller functions and apply them to map(). Since we are updating a stateful object, we cannot use the general map() function. If we want to implement a purer functional programming style, we will use an immutable namedtuple instead of a mutable 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

During our exploration, we will adjust the main body of this conversion function. Perhaps we will start with some of the smallest conversions and derivatives. We will continue to explore with some questions like "Are these correct?". When we find something that does not work, we will take some out of it.

Our overall processing process is as follows:

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

Please note the change in the main body of the statement. The convert() function produces the values we are sure of. We have added some additional variables in the for loop, and we cannot100% certain. Before updating the convert() function, we will see if they are useful (even correct).

Décrémentation

En ce qui concerne le décrement, nous pouvons adopter une méthode de traitement légèrement différente. Nous devons refactoring notre exemple précédent et le transformer en une fonction générateur.

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

Ensuite, un yield a remplacé print().

C'est une autre partie de refactoring.

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

Dans l'idéal, tout notre codage devrait être ainsi. Nous utilisons des fonctions générateurs pour générer des données. L'affichage final des données reste complètement séparé. Cela nous permet de refactoring et de modifier plus librement le traitement.

Maintenant, nous pouvons faire certaines choses, par exemple collecter les lignes dans un objet Counter() ou peut-être calculer certaines statistiques. Nous pouvons utiliser defaultdict(list) pour regrouper les lignes par service.

by_service = defaultdict(list)
pour row dans converted_log("somefile.csv"):
by_service[row.service] = row.response_time
pour svc dans sorted(by_service):
m = statistiques.mean(by_service[svc])
imprimer( "{svc:15s} {m:.2f".format_map(vars()) )"

Nous avons décidé de créer des objets de liste spécifiques ici. Nous pouvons utiliser itertools pour regrouper les temps de réponse par service. Cela semble être une programmation fonctionnelle correcte, mais cette implémentation indique certaines limitations dans le style de programmation fonctionnelle Pythonic. Soit nous devons trier les données (créer des objets de liste), soit créer des listes lors du regroupement des données. Pour effectuer plusieurs statistiques différentes, regrouper les données en créant des listes spécifiques est généralement plus facile.

Nous faisons deux choses maintenant, au lieu de simplement imprimer l'objet de ligne.

Créer quelques variables locales, comme svc et m. Nous pouvons facilement ajouter des variations ou d'autres mesures.

L'utilisation de la fonction vars() sans paramètres crée un dictionnaire à partir des variables locales.

L'usage de vars() sans paramètres est une astuce pratique comme locals(). Il nous permet de créer simplement les variables locales que nous voulons et de les inclure dans la sortie formatée. Nous pouvons pénétrer dans diverses méthodes statistiques que nous pensons pertinentes.

Puisque notre boucle de traitement de base est destinée aux lignes de converted_log (“somefile.csv”), nous pouvons explorer de nombreuses options de traitement à travers un script modifiable. Nous pouvons explorer certaines hypothèses pour déterminer pourquoi certains RESTful API sont lents, tandis que d'autres sont rapides.

Résumé

Ce que j'ai présenté à l'éditeur aujourd'hui est l'analyse exploratoire des données en Python (fonctionnelle), j'espère qu'il vous sera utile. Si vous avez des questions, n'hésitez pas à me laisser un message, je vous répondrai à temps. Je tiens également à remercier chaleureusement tous ceux qui soutiennent le tutoriel de cri.

Déclaration : Le contenu de cet article est issu du réseau, propriété de ses auteurs respectifs, le contenu est apporté par les utilisateurs d'Internet de manière volontaire et téléversé, ce site ne détient pas de droits de propriété, n'a pas été traité par l'éditeur humain et n'assume aucune responsabilité juridique connexe. Si vous trouvez du contenu suspect de violation de droits d'auteur, veuillez envoyer un e-mail à : notice#oldtoolbag.com (veuillez remplacer # par @ lors de l'envoi d'un e-mail pour signaler une violation, et fournir des preuves pertinentes. Une fois vérifié, ce site supprimera immédiatement le contenu suspect de violation de droits d'auteur.)

Vous pourriez aussi aimer