CSV-Dateien effizient vergleichen mit pandas

Hier ein bisschen Python-Code, um zwei CSV Dateien miteinander zu vergleichen. Die Ergebnisse des spalten- und zeilenweisen Vergleichs werden dann zusammengefasst dargestellt, um schnell einen Überblick zu bekommen, wo eine tiefergehende Analyse notwendig ist.

import sys
import collections
import pandas as pd
from tabulate import tabulate
 
 
file1 = pd.read_csv('file1.csv', sep=';', encoding='UTF-8')
file2 = pd.read_csv('file2.csv', sep=';', encoding='UTF-8')
 
columnnames1 = list(file1)
columnnames2 = list(file2)
 
if collections.Counter(columnnames1) == collections.Counter(columnnames2):
    print ("Number of columns and Names match, Comparison possible...\n\n")
else:
    print ("Number of columns and Names are not matching!!! Please check the input!")
    sys.exit('Error!')
 
# add suffixes to distinguish between actual and expected in the merger
file1 = file1.add_suffix('_e') # expected
file2 = file2.add_suffix('_t') # t
 
 
# merge them using the given key, use outer join
comparison = pd.merge(file1,file2, how='outer',
                      left_on=['Key_e'],
                      right_on=['Key_t'])
 
# create the columnwise comparison
for col in columnnames1:
    comparison[(col + '_c')] = comparison[(col + '_t')] == comparison[(col + '_e')]
 
# reorder the columns
comparison=comparison.reindex(sorted(comparison.columns),axis=1)
 
print(tabulate(comparison, tablefmt="pipe", headers="keys"))
 
 
# save the result as Excel file
comparison.to_excel('result.xlsx')
 
# names of the comparison column
check_colnames= [s + '_c' for s in columnnames1]
 
# initialize an empty dataframe for the log
logdf=pd.DataFrame(index=[True,False])
 
for column in check_colnames:
    t=comparison[column].value_counts() # returns a series
    tt=pd.DataFrame(t) # makes a DF out of the series
    logdf = logdf.join(tt,how='outer') # join the two dfs
 
# transpose for better readability
logdf = logdf.transpose()
 
# Ensure fixed sequence of the columns
logdf=logdf.reindex(sorted(logdf.columns),axis=1)
 
# write to disk
logdf.to_excel('logfile.xlsx')
 
# for better viewing on the screen
logdf.fillna('-',inplace=True)
pd.options.display.float_format = '{:,.0f}'.format
 
print(tabulate(logdf, tablefmt="pipe", headers="keys"))

Powershell: Dateien finden, die die 256-Zeichen-Pfadlänge sprengen

Sharepoint-Server haben ein Maximum von 256 Zeichen für Pfadangaben, Leerzeichen in Datei- und Ordnernamen werden dabei als „%20“ dargestellt, sodass für jedes Leerzeichen drei Zeichen „draufgehen“. Mit dem folgenden Powershell-Skript kann man die Dateien in einem Verzeichnis identifizieren, die das Limit vermutlich sprengen.

Die erzeugte CSV-Datei hat drei Spalten: Pfadlänge, Pfadlänge wenn Leerzeichen als „%20“ dargestellt werden, Pfad.

gci "K:\inputpath" | Select @{N="Path Length";E={$_.FullName.Length}}, @{N="URL Length";E={$_.FullName.replace(' ','+++').Length}}, 
Fullname | Export-Csv -NoTypeInformation -Delimiter ";" -Path "t:\ABC\filelaengen.csv"

Excels Wenn ohne Wenn

Vor ein paar Wochen hat jemand in einer Facebook-Gruppe gefragt, wie man — abhängig von einer Soll/Haben-Spalte — in Excel einen Wert mit positivem oder negativem Vorzeichen darstellen kann. Die Lösung ist ganz einfach, wenn man die WENN() Funktion kennt.

Spannender ist die Frage: Geht es auch ohne Wenn() und ohne VBA? Die Antwort ist ja, von hinten durch die Brust ins Auge…

Mittels CODE() Funktion ermitteln wir den ASCII Code des Buchstabens, bei „S“ ist das 83, bei „H“ 72.

Als nächstes ziehen wir von diesem Wert 84 ab und vergleichen den Wert mit -1. Excel wertet dies für „S“ als WAHR aus, für „H“ als FALSCH. Da man mit WAHR (=1) und FALSCH (=0) prima in Excel weiterrechnen kann, multiplizieren wir den Wert mit -2 und addieren 1.

Für „S“ ergibt sich 1 (für „WAHR“) * -2 = -2 + 1 = 1, für „H“ ergibt sich 0 (für „FALSCH“) * -2 = 0 + 1 = 1. Damit muss man dann nur noch den ursprünglichen Betrag multiplizieren…

Gittermuster erstellen mit gridpapers

Hier ein Beispiel dafür, wie man mit dem gridpapers Paket Muster auf Papier bringen kann, hier ein Punktmuster.

%!TEX TS-program = Arara
% arara: pdflatex: {shell: yes}
\documentclass[12pt,ngerman]{scrartcl}
\areaset{8cm}{16cm}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{babel}
\pagestyle{empty}
\usepackage[pattern=dot,% 
colorset=std,
geometry={left=2.25cm,right=1.25cm,top=1cm,bottom=1.25cm},%
textarea,%
patternsize={5mm},%
dotsize={1pt}
]{gridpapers}
 
\begin{document}
~
 
\end{document}

Auszug aus der A4-Seite:

Dymo Labels mit LaTeX und ticket.sty gestalten

Hier ein kurzes Beispiel, wie man mit LaTeX auch Dymo-Labels erzeugen kann, im Beispiel für die Label-Größe 57*32mm. Zum allgemeinen Verständnis von ticket.sty siehe auch meinen DTK Artikel in Ausgabe 1/2021.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{graphicx}
\usepackage[landscape,paperheight=57mm,paperwidth=32mm,left=0mm,top=0mm,right=0mm,bottom=0mm]{geometry}
%\usepackage[sfdefault]{plex-sans}
\usepackage{palatino}
 
\begin{filecontents}[overwrite]{Dymo5732.tdf}
\unitlength=1mm
\hoffset=-25.4mm
\voffset=-29mm
\ticketNumbers{1}{1}
\ticketSize{57}{32} % Breite und Hoehe der Labels in mm
\ticketDistance{0}{0} % Abstand der Labels
\end{filecontents}
 
\usepackage[Dymo5732]{ticket}
 
\renewcommand{\ticketdefault}{}%
\makeatletter
\@boxedfalse % Rahmen um Ticket
\@emptycrossmarkfalse % Falzmarken
\@cutmarkfalse % Schnittmarken
\makeatother
 
\newcommand{\myticket}[2]{
\ticket{%
\put(10,10){\scalebox{#1}{\bfseries #2}}
}}
 
\newcommand{\myticketml}[4]{
\ticket{%
\put(5,20){\scalebox{#1}{\bfseries #2}}
\put(5,15){\scalebox{#1}{\bfseries #3}}
\put(5,10){\scalebox{#1}{\bfseries #4}}
}}
 
 
\begin{document}
\myticketml{1.25}{Dr. Max  Mustermann}{Musterweg 123}{54321~Musterstadt}
\myticket{2}{Steuern}
\end{document}

PDF

Nützliche Kommandozeilen-Einzeiler für LaTeX

Unter https://gist.github.com/iwishiwasaneagle/2f91f63f3cb0107b94b501aa284a18ca gibt es eine Sammlung an nützlichen Einzeilern für die Arbeit mit LaTeX.

Windows-Uhr: Sekundenanzeige aktivieren

Die Uhr rechts unten in der Windows-Taskleiste kann man auch so einstellen, dass die Sekunden angezeigt werden

Dazu mit regedit den Schlüssel \HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced –> ShowSecondInSystemClock auf 1 setzen.

Aktienkurse mit Python und LaTeX auswerten

Hier ein einfaches Beispiel, wie man mit Python und LaTeX ein PDF mit Kursinformationen erstellen kann.

Zuerst der Python-Teil, der die Apple-Kursdaten seit dem 1.1.2021 in einen Dataframe lädt und dann in eine LaTeX-Tabelle schreibt:

import pandas
import pandas_datareader.data as web
 
YAHOO_TODAY="http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=sd1ohgl1vl1"
 
history = web.DataReader('AAPL', "yahoo", start="2021-1-1")
history.to_latex('aapl.tex')

Dann noch der LaTeX-Teil, der a) den Python-Code aus dem LaTeX-Lauf heraus ausführt und b) die erzeugte Tabellen-Datei nur dann einbindet, wenn sie wirklich auch erzeugt wurde.

\documentclass[12pt,ngerman]{scrartcl}
\usepackage[a4paper, top=1cm,bottom=1cm,left=1cm, right=1cm]{geometry}
\usepackage[T1]{fontenc}
\usepackage{booktabs}
 
\makeatletter
\newcommand{\testfileexists}[1]{%
  \IfFileExists{#1}%
    {\def\inputtestedfile{\@@input #1 }}
    {\let\inputtestedfile\@empty}%
}
\makeatother
 
\begin{document}
 
\write18{python runpy.py}
 
  \testfileexists{aapl}
      \inputtestedfile
 
 
\end{document}

Stripplots mit Seaborn

This entry is part 3 of 3 in the series Seaborn

Mit Seaborn lassen sich auch Stripplots erstellen, hier ein Beispiel. Die Besonderheit ist hier, dass die pd.melt() Funktion genutzt wird, um aus den verschiedenen Variablen des Datensatzes drei Variablen zu machen: eine für den Typ echt/unecht, eine für den Variablennamen und eine für den Wert der jeweiligen Variablen.

#!/usr/bin/env python
# coding: utf-8
 
import seaborn as sns
import pandas as pd
import requests
from bs4 import BeautifulSoup
from io import StringIO
import matplotlib.pylab as plt
 
headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Max-Age': '3600',
    'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0'
}
 
url = "http://www.statistics4u.com/fundstat_eng/data_fluriedw.html"
req = requests.get(url, headers)
soup = BeautifulSoup(req.content, 'html.parser')
 
 
data=soup.find('pre').contents[0]
 
str_object = StringIO(data)
 
df = pd.read_csv(str_object,engine='python',skiprows=5,delim_whitespace=True)
 
# Banknotes BN1 to BN100 are genuine, all others are counterfeit
df['Type'] = 'Counterfeit'
df.loc[df.index[:100], 'Type'] = 'Genuine'
 
print(df)
 
sns.set(style="whitegrid", palette="muted")
 
#df = df[['Left', 'Diagonal', 'Type']]
df = pd.melt(df, "Type", var_name="Variable")
 
sp = sns.stripplot(x="value", 
              y="Variable", 
              hue="Type",
              data=df, 
              dodge=True, 
              alpha=.75, 
              zorder=1)
 
#sp.set(xlim=(127, 143)) 
 
sp.legend_.remove()
plt.show()

Das Bild, was dabei erzeugt wird, ist aber eher schlecht. Da die Variablen teilweise sehr unterschiedliche Skalen haben, erkennt man eigentlich nur Punktwolken, die übereinander liegen.

Die Lösung besteht darin, nur die Variablen gemeinsam zu plotten, die sehr nah beieinander liegende Skalen haben. Dazu entfernt man die beiden Hashes aus den auskommentierten Python-Zeilen, um nur noch die Variablen Left und Diagonal zu plotten und um die Skale anzupassen.

Dann erkennt man im Bild, dass die Diagonale echte und falsche Banknoten schön voneinander trennt.

Mit StringIO Python-Objekte als Datei nutzen

Im Beitrag „CSV-Dateien mit speziellen Spaltentrennern in Python laden“ hatte ich gezeigt, wie man mit BS4 Dateien aus Webseiten extrahieren und abspeichern kann, um sie dann in pandas weiterzuverarbeiten. Es geht auch ohne den Umweg der CSV-Datei, wenn man die StringIO Klasse aus dem io Modul nutzt.

Wir laden das Modul und instanziieren dann ein Objekt der Klasse mit dem von BS4 gefundenen Datensatz. Diese Objekt wird dann anstelle des Pfades der CSV-Datei an die pd.read_csv() Funktion übergeben.

import pandas as pd
import requests
from bs4 import BeautifulSoup
from io import StringIO
 
headers = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET',
    'Access-Control-Allow-Headers': 'Content-Type',
    'Access-Control-Max-Age': '3600',
    'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0'
}
 
url = "http://www.statistics4u.com/fundstat_eng/data_fluriedw.html"
req = requests.get(url, headers)
soup = BeautifulSoup(req.content, 'html.parser')
 
data=soup.find('pre').contents[0]
 
str_object = StringIO(data)
 
df = pd.read_csv(str_object,engine='python',skiprows=5,delim_whitespace=True)
print(df)