Er zijn veel situaties waarin je snel grote bestanden wilt analyseren, wijzigen en verwerken, zowel in aantal als in omvang. Bestanden zijn echter vaak tekstgebaseerde CSV-bestanden (Comma Separated Values). Ze openen met standaard spreadsheet-apps zoals Excel, LibreOffice of OpenOffice overbelast het geheugen van je machine. Ook het in één batch verwerken van een groot aantal bestanden gaat vaak na een paar uur mis door een onverwachte bestandsafwijking.
Je brengt onvermijdelijk veel tijd door met vastgelopen schermen, herstarts en lange wachttijden.
Toch kunnen de meeste van deze taken worden uitgevoerd met een paar regels code. Sterker nog, vertrouwdheid met een paar simpele shellopdrachten kan je veel tijd en frustratie besparen.
In deze post geef ik een overzicht van enkele shellopdrachten die ik bijna elke dag gebruik. Hopelijk heb je er wat aan.
Korte notities
Let op dat er verschillende typen shells zijn (bash, zsh, ...), waarbij bash het meest voorkomt omdat het de standaard shell is op OS X en de meeste Linux-distributies. Zsh (en Oh My Zsh) is een populaire en krachtige alternatieve shell. De shellopdrachten in deze blogpost zijn getest op bash op OS X (macOS) en zouden ook in andere shells en omgevingen moeten werken.
Alle onderstaande voorbeelden zijn gebaseerd op de adult dataset uit de UCI Machine Learning-repository, ook wel de "Census Income"-dataset genoemd en hier beschikbaar. Deze dataset wordt vaak gebruikt om te voorspellen of het inkomen hoger is dan \$50K/jaar op basis van censusgegevens. Met 48842 rijen en 14 attributen is het zeker geen grote dataset, maar wel voldoende om de voorbeelden te illustreren. Hoewel de data de extensie .data heeft, is het een goed geformatteerd CSV-bestand. Voel je vrij om de dataset te downloaden en mee te doen!
Tellen met wc
Bij een nieuw tekstbestand wil je weten hoeveel regels het bevat. Dat kan met de word count-opdracht wc -l:
Dit vertelt je dat het bestand adult.data 32562 rijen bevat. De vlag -l geeft aan dat wc de regels moet tellen. Maar je kunt met wc ook woorden tellen met de vlag -w.
De opdracht wc kan ook het aantal bestanden in een map tellen door de uitvoer van een simpele ls -l-opdracht als input voor wc te gebruiken. De uitvoer van de ene opdracht als invoer voor een andere gebruiken via het pipe-symbool | is een handig shellpatroon dat pipelining heet. Je ziet dit in meerdere voorbeelden in deze post.
Stel dat je nu een map met veel bestanden hebt. Met de volgende opdracht zie je precies hoeveel bestanden erin staan:
Er zijn veel andere toepassingen voor wc als je wildcards en submappen gaat gebruiken.
Laten we teruggaan naar je bestand adult.data en met de opdracht head naar de eerste regels kijken. Standaard geeft head 10 regels weer; je beperkt de uitvoer tot de eerste 2 regels met de vlag -n.
En je ziet dat het bestand geen headerregel heeft. De kolomnamen staan niet in het bestand. Je kunt die headerregel toevoegen door twee bestanden te concatenaten met de opdracht cat.
Bestanden samenvoegen met cat
De opdracht cat print de inhoud van een bestand naar de standaarduitvoer (oftewel je terminal). Je kunt er ook een reeks bestanden mee samenvoegen. De opdracht cat file_1.csv file_2.csv > target_file.csv voegt de inhoud van zowel file_1.csv als file_2.csv samen in target_file.csv, waarbij file_2.csv achter file_1.csv wordt geplakt.
Het headerbestand zit niet in de originele dataset en moet je aanmaken. Doe dat door de kommagescheiden lijst met kolomnamen te echoen naar een bestand header.csv.
$ echo"age,workclass,fnlwgt,education,education-num,marital-status,occupation,relationship,race,sex,capital-gain,capital-loss,native-country,class"> header.csvMogelijk gemaakt door
In dit voorbeeld heb je de shell-omleiding > gebruikt om de uitvoer van cat naar het bestand adult.csv te sturen. De > maakt een bestand aan of overschrijft de inhoud volledig als het bestand al bestaat. Door het symbool te verdubbelen >> voeg je nieuwe inhoud toe aan een bestaand bestand zonder de inhoud te verwijderen.
Laten we nu het bestand header.csv aan het begin van adult.data toevoegen. Tegelijkertijd hernoem je adult.data naar adult.csv omdat het tenslotte een CSV-bestand is.
Een andere veelvoorkomende datasituatie doet zich voor wanneer een bestand corrupt of slecht geformatteerd is, bijvoorbeeld met niet-UTF-8-tekens of een komma op de verkeerde plek. Je kunt dat bestand corrigeren zonder het te openen met de opdracht sed.
Het generieke sed-patroon is
$ sed"s/<string to replace>/<string to replace it with>/g"<source_file>><target_file>.Mogelijk gemaakt door
De dataset adult.csv gebruikt het teken ? om een ontbrekende waarde aan te duiden. Je kunt dat teken vervangen door een beter passende standaardwaarde met sed. De lege string is te verkiezen als aanduiding van een ontbrekende waarde, omdat die als NaN wordt geïnterpreteerd bij het inladen van de data in een Pandas DataFrame.
Dit geeft je een telling van 2399 regels met minstens één kolom met een ontbrekende waarde aangeduid door ?. De volgende opdracht vervangt alle kolommen met ? door een lege string. Alle cellen die alleen ? bevatten, gevolgd door een spatie, zijn nu echt leeg.
Let op dat je de kolomscheidingsteken , in de bron- en doelstrings gebruikt om te voorkomen dat je legitieme vraagtekens vervangt die elders in de dataset kunnen staan.
Een groot bestand subsetten
Stel je werkt nu met een bestand met meer dan 30 miljoen rijen. Terwijl je je Python- of R-script maakt, wil je je workflow testen op slechts een sample van dat grote oorspronkelijke bestand en niet de hele dataset in het geheugen laden. Je kunt een combinatie van head en tail gebruiken om een subsample van de originele dataset te maken.
head geeft het eerste deel van bestanden weer;
tail geeft het laatste deel van bestanden weer
Zoals je eerder zag, nemen deze opdrachten een vlag -n, wat staat voor --lines, die het aantal regels in de uitvoer beperkt. tail heeft ook een vlag -f waarbij f staat voor --follow, die nieuwe regels toont zodra ze aan het einde van het bestand worden toegevoegd. Dit is vooral handig om logbestanden te monitoren terwijl je scripts draaien met tail -f <logfile>.
Door het pipe-symbool | te combineren met head en tail, kun je een bepaald aantal regels uit een bronbestand halen en de inhoud exporteren naar een subsetbestand. Bijvoorbeeld, om 20 regels te extraheren vanaf regel 100:
Let op dat je eerst de first_line + number_of_lines pakt met head en daarna met tail de number_of_lines. De generieke subset-opdracht is:
$ head -n <total_lines><source_file>| tail -n <number_of_lines>><target_file>Mogelijk gemaakt door
waarbij total_lines = first_line + number_of_lines. Je nieuwe samplebestand bevat echter niet langer de headerregel. We weten al hoe we de header terugzetten in het subsetbestand met cat om het subsetbestand en het eerder gemaakte header.csv te concatenaten:
$ cat adult_sample.csv header.csv > adult_sample_with_header.csv
Mogelijk gemaakt door
Let op dat je de bronbestandsnaam adult\_sample.csv niet als doelbestandsnaam hebt gebruikt, maar in plaats daarvan een nieuw bestand hebt gemaakt met de naam adult\_sample\_with\_header.csv. Dezelfde naam gebruiken als een van de bronnen én als doel bij cat leidt tot onverwachte bestandsinhoud. Maak beter een nieuw bestand als uitvoer van de opdracht cat.
Duplicaten vinden met uniq
Met de opdracht uniq kun je naast elkaar staande herhaalde regels in een bestand vinden. uniq kent meerdere vlaggen, waarbij de meest nuttige zijn:
uniq -c: voegt het aantal herhalingen toe aan elke regel;
uniq -d: geeft alleen dubbele regels weer; en
uniq -u: geeft alleen unieke regels weer.
Maar uniq is geen slimme opdracht. Herhaalde regels worden niet gedetecteerd als ze niet naast elkaar staan. Dat betekent dat je het bestand eerst moet sorten. Deze opdracht telt het aantal dubbele regels in adult.csv.
en laat zien dat er 23 duplicaten zijn. De volgende opdracht neemt de uitvoer van alle regels met herhaaltellingen, sorteert omgekeerd en geeft de eerste 3 duplicaten weer:
Er zijn veel krachtige mogelijkheden door sort en uniq met verschillende vlaggen te combineren. Gebruik man sort en man uniq om deze opdrachten verder te verkennen.
Kolommen selecteren met cut
Het mooie van CSV-bestanden en shellopdrachten is dat je ook op kolomniveau kunt werken met cut om een bepaalde kolom te selecteren. cut heeft twee hoofdvlaggen: -d om de kolomscheidingsteken te specificeren en -f om de kolommen op te geven waarmee je wilt werken. In het volgende voorbeeld gebruik je cut om het aantal unieke waarden te vinden van de categorische variabele workclass (kolom 2).
Selecteer eerst de kolom workclass en pipe naar head om te controleren of je de juiste kolom hebt:
$ cut-d","-f2 adult.csv |head-3
workclass
State-gov
Self-emp-not-incMogelijk gemaakt door
Om nu uniques te tellen, sorteer je de uitvoer van cut en pipe je het resultaat naar uniq -c, als volgt:
Dit vertelt je bijvoorbeeld dat je 1837 null-waarden hebt, en dat de hoofdklasse veruit de Private-klasse is met 22969 voorkomens.
De bovenstaande opdrachtregel lijkt op de value_counts()-methode toegepast op een DataFrame met de adult.csv-data.
Lussen
Tot nu toe heb je vooral met één bestand gewerkt. Om grote aantallen bestanden te hernoemen, verwerken of verplaatsen, voeg je lussen toe aan je shell-toolkit. Het generieke formaat van een lus in bash is:
Laten we dat toepassen. Stel je hebt 1000 bestanden met spaties in de bestandsnaam en je wilt elke spatie vervangen door een underscore "_".
replace_source=' 'replace_target='_'forfilenamein ./*.csv;donew_filename=${filename//$replace_source/$replace_target}mv"$filename""$new_filename"doneMogelijk gemaakt door
In dit voorbeeld:
Declareer je eerst twee variabelen: replace_source en replace_target als placeholders voor het spatie- en underscoreteken
Loop je over alle *.csv-bestanden in de huidige map
maak je voor elke filename een new_filename door elke spatie te vervangen door een underscore
en hernoem je het bestand van de huidige bestandsnaam naar de nieuwe bestandsnaam met de opdracht mv
Je hebt hier niet alleen de shell gebruikt om door bestanden in een map te lopen, maar ook variabelen aangemaakt.
Variabelen
We sluiten deze introductie tot shellopdrachten af met variabelen. Een variabele aanmaken in shell doe je simpelweg met
Let op dat er geen spaties rond het '='-teken staan. var = '<a string>' zou niet werken. Om de waarde van de variabele te tonen, echo je die simpelweg:
Het schrijven van lussen en het gebruiken van variabelen opent de deur naar complexere bestandsmanipulaties en is een opstap naar shellscripting, wat buiten deze introductie valt. Shellscripting is echter prima te doen als je klein begint en verder uitbreidt naarmate je vertrouwen groeit.
Conclusie
Shellopdrachten zijn enorm nuttig in je dagelijkse werk als data scientist. Er zijn nog veel meer voorbeelden en use-cases te verkennen. En zoals je zult zien, zijn er vaak verschillende manieren om een bepaald resultaat te bereiken met een breed scala aan shellopdrachten en bijbehorende vlaggen. In dat geval is het devies: houd het simpel — dat wint altijd.
Hopelijk helpen de voorbeelden hier je om shellopdrachten te integreren in je dataprocestoolkit. Laat gerust iets van je horen en deel je shelltrucs met me op Twitter: @alexip.
Er zijn veel situaties waarin je snel grote bestanden wilt analyseren, wijzigen en verwerken, zowel in aantal als in omvang. Bestanden zijn echter vaak tekstgebaseerde CSV-bestanden (Comma Separated Values). Ze openen met standaard spreadsheet-apps zoals Excel, LibreOffice of OpenOffice overbelast het geheugen van je machine. Ook het in één batch verwerken van een groot aantal bestanden gaat vaak na een paar uur mis door een onverwachte bestandsafwijking.
Je brengt onvermijdelijk veel tijd door met vastgelopen schermen, herstarts en lange wachttijden.
Toch kunnen de meeste van deze taken worden uitgevoerd met een paar regels code. Sterker nog, vertrouwdheid met een paar simpele shellopdrachten kan je veel tijd en frustratie besparen.
In deze post geef ik een overzicht van enkele shellopdrachten die ik bijna elke dag gebruik. Hopelijk heb je er wat aan.
Korte notities
Let op dat er verschillende typen shells zijn (bash, zsh, ...), waarbij bash het meest voorkomt omdat het de standaard shell is op OS X en de meeste Linux-distributies. Zsh (en Oh My Zsh) is een populaire en krachtige alternatieve shell. De shellopdrachten in deze blogpost zijn getest op bash op OS X (macOS) en zouden ook in andere shells en omgevingen moeten werken.
Alle onderstaande voorbeelden zijn gebaseerd op de adult dataset uit de UCI Machine Learning-repository, ook wel de "Census Income"-dataset genoemd en hier beschikbaar. Deze dataset wordt vaak gebruikt om te voorspellen of het inkomen hoger is dan \$50K/jaar op basis van censusgegevens. Met 48842 rijen en 14 attributen is het zeker geen grote dataset, maar wel voldoende om de voorbeelden te illustreren. Hoewel de data de extensie
.dataheeft, is het een goed geformatteerd CSV-bestand. Voel je vrij om de dataset te downloaden en mee te doen!Tellen met
wcBij een nieuw tekstbestand wil je weten hoeveel regels het bevat. Dat kan met de word count-opdracht
wc -l:Dit vertelt je dat het bestand
adult.data32562 rijen bevat. De vlag-lgeeft aan datwcde regels moet tellen. Maar je kunt metwcook woorden tellen met de vlag-w.Het bestand
adult.databevat bijna 500k woorden.De opdracht
wckan ook het aantal bestanden in een map tellen door de uitvoer van een simpelels -l-opdracht als input voorwcte gebruiken. De uitvoer van de ene opdracht als invoer voor een andere gebruiken via het pipe-symbool|is een handig shellpatroon dat pipelining heet. Je ziet dit in meerdere voorbeelden in deze post.Stel dat je nu een map met veel bestanden hebt. Met de volgende opdracht zie je precies hoeveel bestanden erin staan:
Er zijn veel andere toepassingen voor
wcals je wildcards en submappen gaat gebruiken.Laten we teruggaan naar je bestand
adult.dataen met de opdrachtheadnaar de eerste regels kijken. Standaard geefthead10 regels weer; je beperkt de uitvoer tot de eerste 2 regels met de vlag-n.En je ziet dat het bestand geen headerregel heeft. De kolomnamen staan niet in het bestand. Je kunt die headerregel toevoegen door twee bestanden te concatenaten met de opdracht
cat.Bestanden samenvoegen met
catDe opdracht
catprint de inhoud van een bestand naar de standaarduitvoer (oftewel je terminal). Je kunt er ook een reeks bestanden mee samenvoegen. De opdrachtcat file_1.csv file_2.csv > target_file.csvvoegt de inhoud van zowelfile_1.csvalsfile_2.csvsamen intarget_file.csv, waarbijfile_2.csvachterfile_1.csvwordt geplakt.Het headerbestand zit niet in de originele dataset en moet je aanmaken. Doe dat door de kommagescheiden lijst met kolomnamen te
echoen naar een bestandheader.csv.In dit voorbeeld heb je de shell-omleiding
>gebruikt om de uitvoer vancatnaar het bestandadult.csvte sturen. De>maakt een bestand aan of overschrijft de inhoud volledig als het bestand al bestaat. Door het symbool te verdubbelen>>voeg je nieuwe inhoud toe aan een bestaand bestand zonder de inhoud te verwijderen.Laten we nu het bestand
header.csvaan het begin vanadult.datatoevoegen. Tegelijkertijd hernoem jeadult.datanaaradult.csvomdat het tenslotte een CSV-bestand is.Controleer of de eerste rij van
adult.csvde kolomnamen bevat met:Een bestand aanpassen met
sedEen andere veelvoorkomende datasituatie doet zich voor wanneer een bestand corrupt of slecht geformatteerd is, bijvoorbeeld met niet-UTF-8-tekens of een komma op de verkeerde plek. Je kunt dat bestand corrigeren zonder het te openen met de opdracht
sed.Het generieke
sed-patroon isDe dataset
adult.csvgebruikt het teken?om een ontbrekende waarde aan te duiden. Je kunt dat teken vervangen door een beter passende standaardwaarde metsed. De lege string is te verkiezen als aanduiding van een ontbrekende waarde, omdat die alsNaNwordt geïnterpreteerd bij het inladen van de data in een Pandas DataFrame.Dit geeft je een telling van 2399 regels met minstens één kolom met een ontbrekende waarde aangeduid door
?. De volgende opdracht vervangt alle kolommen met?door een lege string. Alle cellen die alleen?bevatten, gevolgd door een spatie, zijn nu echt leeg.Let op dat je de kolomscheidingsteken
,in de bron- en doelstrings gebruikt om te voorkomen dat je legitieme vraagtekens vervangt die elders in de dataset kunnen staan.Een groot bestand subsetten
Stel je werkt nu met een bestand met meer dan 30 miljoen rijen. Terwijl je je Python- of R-script maakt, wil je je workflow testen op slechts een sample van dat grote oorspronkelijke bestand en niet de hele dataset in het geheugen laden. Je kunt een combinatie van
headentailgebruiken om een subsample van de originele dataset te maken.headgeeft het eerste deel van bestanden weer;tailgeeft het laatste deel van bestanden weerZoals je eerder zag, nemen deze opdrachten een vlag
-n, wat staat voor--lines, die het aantal regels in de uitvoer beperkt.tailheeft ook een vlag-fwaarbijfstaat voor--follow, die nieuwe regels toont zodra ze aan het einde van het bestand worden toegevoegd. Dit is vooral handig om logbestanden te monitoren terwijl je scripts draaien mettail -f <logfile>.Door het pipe-symbool
|te combineren metheadentail, kun je een bepaald aantal regels uit een bronbestand halen en de inhoud exporteren naar een subsetbestand. Bijvoorbeeld, om 20 regels te extraheren vanaf regel 100:Let op dat je eerst de first_line + number_of_lines pakt met
headen daarna mettailde number_of_lines. De generieke subset-opdracht is:waarbij
total_lines = first_line + number_of_lines. Je nieuwe samplebestand bevat echter niet langer de headerregel. We weten al hoe we de header terugzetten in het subsetbestand metcatom het subsetbestand en het eerder gemaakteheader.csvte concatenaten:Let op dat je de bronbestandsnaam
adult\_sample.csvniet als doelbestandsnaam hebt gebruikt, maar in plaats daarvan een nieuw bestand hebt gemaakt met de naamadult\_sample\_with\_header.csv. Dezelfde naam gebruiken als een van de bronnen én als doel bijcatleidt tot onverwachte bestandsinhoud. Maak beter een nieuw bestand als uitvoer van de opdrachtcat.Duplicaten vinden met
uniqMet de opdracht
uniqkun je naast elkaar staande herhaalde regels in een bestand vinden.uniqkent meerdere vlaggen, waarbij de meest nuttige zijn:uniq -c: voegt het aantal herhalingen toe aan elke regel;uniq -d: geeft alleen dubbele regels weer; enuniq -u: geeft alleen unieke regels weer.Maar
uniqis geen slimme opdracht. Herhaalde regels worden niet gedetecteerd als ze niet naast elkaar staan. Dat betekent dat je het bestand eerst moetsorten. Deze opdracht telt het aantal dubbele regels inadult.csv.en laat zien dat er 23 duplicaten zijn. De volgende opdracht neemt de uitvoer van alle regels met herhaaltellingen, sorteert omgekeerd en geeft de eerste 3 duplicaten weer:
Er zijn veel krachtige mogelijkheden door
sortenuniqmet verschillende vlaggen te combineren. Gebruikman sortenman uniqom deze opdrachten verder te verkennen.Kolommen selecteren met
cutHet mooie van CSV-bestanden en shellopdrachten is dat je ook op kolomniveau kunt werken met
cutom een bepaalde kolom te selecteren.cutheeft twee hoofdvlaggen:-dom de kolomscheidingsteken te specificeren en-fom de kolommen op te geven waarmee je wilt werken. In het volgende voorbeeld gebruik jecutom het aantal unieke waarden te vinden van de categorische variabeleworkclass(kolom 2).Selecteer eerst de kolom
workclassen pipe naar head om te controleren of je de juiste kolom hebt:Om nu uniques te tellen, sorteer je de uitvoer van cut en pipe je het resultaat naar
uniq -c, als volgt:Dit vertelt je bijvoorbeeld dat je 1837 null-waarden hebt, en dat de hoofdklasse veruit de
Private-klasse is met 22969 voorkomens.De bovenstaande opdrachtregel lijkt op de
value_counts()-methode toegepast op een DataFrame met deadult.csv-data.Lussen
Tot nu toe heb je vooral met één bestand gewerkt. Om grote aantallen bestanden te hernoemen, verwerken of verplaatsen, voeg je lussen toe aan je shell-toolkit. Het generieke formaat van een lus in bash is:
Laten we dat toepassen. Stel je hebt 1000 bestanden met spaties in de bestandsnaam en je wilt elke spatie vervangen door een underscore "_".
In dit voorbeeld:
replace_sourceenreplace_targetals placeholders voor het spatie- en underscoreteken*.csv-bestanden in de huidige mapfilenameeennew_filenamedoor elke spatie te vervangen door een underscoremvJe hebt hier niet alleen de shell gebruikt om door bestanden in een map te lopen, maar ook variabelen aangemaakt.
Variabelen
We sluiten deze introductie tot shellopdrachten af met variabelen. Een variabele aanmaken in shell doe je simpelweg met
Bijvoorbeeld:
Let op dat er geen spaties rond het '='-teken staan.
var = '<a string>'zou niet werken. Om de waarde van de variabele te tonen,echoje die simpelweg:En om hem in een script te gebruiken, zet je hem tussen aanhalingstekens zoals je eerder zag:
Het schrijven van lussen en het gebruiken van variabelen opent de deur naar complexere bestandsmanipulaties en is een opstap naar shellscripting, wat buiten deze introductie valt. Shellscripting is echter prima te doen als je klein begint en verder uitbreidt naarmate je vertrouwen groeit.
Conclusie
Shellopdrachten zijn enorm nuttig in je dagelijkse werk als data scientist. Er zijn nog veel meer voorbeelden en use-cases te verkennen. En zoals je zult zien, zijn er vaak verschillende manieren om een bepaald resultaat te bereiken met een breed scala aan shellopdrachten en bijbehorende vlaggen. In dat geval is het devies: houd het simpel — dat wint altijd.
Hopelijk helpen de voorbeelden hier je om shellopdrachten te integreren in je dataprocestoolkit. Laat gerust iets van je horen en deel je shelltrucs met me op Twitter: @alexip.
Bekijk ook onze tutorial Een data science-omgeving op je computer instellen.