Hay muchas situaciones en las que necesitas analizar, modificar y procesar rápidamente gran cantidad de archivos, tanto en número como en tamaño. A menudo, además, son archivos de texto en formato CSV (valores separados por comas). Abrirlos con hojas de cálculo como Excel, LibreOffice u OpenOffice satura la memoria del equipo. Procesar muchos archivos en un solo lote también suele fallar tras unas horas por alguna anomalía inesperada.
Al final acabas perdiendo tiempo entre bloqueos, reinicios y esperas eternas.
Sin embargo, la mayoría de estas tareas podrían resolverse con unas pocas líneas de código. Es más, dominar unas cuantas órdenes básicas de shell puede ahorrarte mucho tiempo y frustración.
En este post te presento un resumen de varios comandos de shell que uso casi a diario. Espero que te resulten útiles.
Notas rápidas
Ten en cuenta que existen diferentes tipos de shell (bash, zsh, ...). Bash es el más común, ya que es el shell predeterminado en OS X y en las principales distribuciones de Linux. Zsh (y Oh My Zsh) es una alternativa muy popular y potente. Los comandos de shell incluidos en este artículo se han probado en bash sobre OS X (macOS) y deberían funcionar en otros shells y entornos.
Todos los ejemplos siguientes se basan en el conjunto de datos adult del repositorio de UCI Machine Learning, también conocido como "Census Income" y disponible aquí. Suele emplearse para predecir si los ingresos superan los \$50K/año a partir de datos censales. Con 48.842 filas y 14 atributos no es un dataset grande, pero es suficiente para ilustrar los ejemplos. Aunque los datos se guardan con la extensión .data, es un CSV bien formateado. ¡Descárgalo si quieres seguir el tutorial paso a paso!
Cuenta con wc
Ante un archivo de texto nuevo, quizá quieras saber cuántas líneas tiene. Puedes hacerlo con el comando de conteo wc -l:
Esto indica que el archivo adult.data contiene 32.562 filas. La opción -l le dice a wc que cuente líneas. También puedes usar wc para contar palabras con la opción -w.
El archivo adult.data contiene casi 500k palabras.
El comando wc también puede contar el número de archivos en un directorio usando como entrada la salida de un simple ls -l. Usar la salida de un comando como entrada de otro mediante la tubería | es un patrón muy útil llamado pipelining. Lo verás varias veces en este post.
Imagina ahora que tienes una carpeta con muchos archivos. El siguiente comando te dirá exactamente cuántos contiene:
Hay muchas más aplicaciones de wc si empiezas a usar comodines y subcarpetas.
Volvamos a tu archivo adult.data y mira las primeras líneas con head. Por defecto, head muestra 10 líneas; puedes limitar la salida a las 2 primeras con la opción -n.
Y verás que el archivo no tiene fila de cabecera: los nombres de columna no están incluidos. Puedes añadirla concatenando dos archivos con el comando cat.
Concatena archivos con cat
El comando cat imprime el contenido de un archivo en la salida estándar (tu terminal). También sirve para concatenar varios archivos. El comando cat file_1.csv file_2.csv > target_file.csv fusionará el contenido de file_1.csv y file_2.csv en target_file.csv, añadiendo file_2.csv al final de file_1.csv.
El archivo de cabecera no está en el dataset original, así que tienes que crearlo. Para ello, haz echo de la lista de nombres de columna separados por comas en un archivo header.csv.
$ echo"age,workclass,fnlwgt,education,education-num,marital-status,occupation,relationship,race,sex,capital-gain,capital-loss,native-country,class"> header.csvCon la tecnología de
En este ejemplo, usaste la redirección de shell > para volcar la salida de cat al archivo adult.csv. El símbolo > crea el archivo o reemplaza por completo su contenido si ya existe. Si lo duplicas >>, se añadirá el nuevo contenido al final sin borrar lo anterior.
Ahora añade el archivo header.csv al principio de adult.data. De paso, renombra adult.data a adult.csv, ya que es, al fin y al cabo, un CSV.
Otro caso típico de minería de datos ocurre cuando un archivo está corrupto o mal formateado, por ejemplo con caracteres no UTF-8 o comas fuera de sitio. Puedes corregirlo sin abrirlo con el comando sed.
El patrón genérico de sed es
$ sed"s/<string to replace>/<string to replace it with>/g"<source_file>><target_file>.Con la tecnología de
El dataset adult.csv usa el carácter ? para indicar valores ausentes. Puedes sustituirlo por un valor por defecto mejor usando sed. La cadena vacía es preferible para marcar valores ausentes, ya que se interpretará como NaN al cargar los datos en un DataFrame de Pandas.
Esto te da un recuento de 2.399 líneas con al menos una columna con un valor ausente indicado por ?. El siguiente comando sustituirá todas las columnas con ? por una cadena vacía. Todas las celdas que solo contienen ? seguido de un espacio quedarán realmente vacías.
Fíjate en que usas el delimitador de columna , en las cadenas origen y destino para evitar reemplazar signos de interrogación válidos que puedan aparecer en otras partes del dataset.
Crear un subconjunto de un archivo grande
Imagina ahora que trabajas con un archivo de más de 30 millones de filas. Mientras preparas tu script en Python o R, querrás probar el flujo con una muestra del archivo original y no cargar todo en memoria. Puedes combinar head y tail para crear una submuestra del dataset original.
head muestra la primera parte de los archivos;
tail muestra la última parte
Como ya viste, estos comandos aceptan la opción -n (de --lines) para limitar el número de líneas en la salida. tail también acepta -f (de --follow) para mostrar en tiempo real los datos que se van añadiendo al final del archivo. Es muy útil para monitorizar logs mientras se ejecutan tus scripts con tail -f <logfile>.
Combinando la tubería | con head y tail, puedes extraer un número concreto de líneas de un archivo y exportarlas a un archivo de subconjunto. Por ejemplo, para extraer 20 líneas empezando en la línea 100:
Primero tomas con headfirst_line + number_of_lines y luego aplicas tail para quedarte con number_of_lines. El comando genérico es:
$ head -n <total_lines><source_file>| tail -n <number_of_lines>><target_file>Con la tecnología de
donde total_lines = first_line + number_of_lines. Eso sí, tu muestra ya no tiene la fila de cabecera. Sabemos añadirla de nuevo en el subconjunto con cat concatenando el archivo de muestra y el header.csv que creamos antes:
$ cat adult_sample.csv header.csv > adult_sample_with_header.csv
Con la tecnología de
Fíjate en que no usaste el nombre fuente adult\_sample.csv como nombre de destino, sino que creaste adult\_sample\_with\_header.csv. Usar cat con un destino que coincide con una de las fuentes puede producir resultados inesperados. Mejor crea un archivo nuevo como salida de cat.
Encontrar duplicados con uniq
Con el comando uniq puedes detectar líneas repetidas adyacentes en un archivo. uniq tiene varias opciones útiles:
uniq -c: añade el recuento de repeticiones a cada línea;
uniq -d: muestra solo las líneas duplicadas; y
uniq -u: muestra solo las líneas únicas.
Eso sí, uniq no es "listo": no detecta repeticiones si no son adyacentes. Por tanto, primero hay que sort el archivo. Este comando cuenta el número de líneas duplicadas en adult.csv:
Hay muchas combinaciones potentes si mezclas sort y uniq con distintas opciones. Usa man sort y man uniq para explorar más.
Seleccionar columnas con cut
Lo mejor de los CSV y los comandos de shell es que puedes trabajar a nivel de columna usando cut para seleccionar la que te interese. cut acepta dos opciones principales: -d para indicar el delimitador de columnas y -f para especificar las columnas. En el siguiente ejemplo, usas cut para conocer el número de valores únicos que toma la variable categórica workclass (columna 2).
Primero selecciona la columna workclass y encadena a head para verificar que es la correcta:
$ cut-d","-f2 adult.csv |head-3
workclass
State-gov
Self-emp-not-incCon la tecnología de
Ahora, para contar únicos, ordena la salida de cut y encadénala a uniq -c así:
Esto te indica, por ejemplo, que hay 1.837 valores nulos y que la clase mayoritaria es Private con 22.969 apariciones.
La línea anterior es similar al método value_counts() aplicado a una Serie con los datos de adult.csv.
Bucles
Hasta ahora, has trabajado sobre un único archivo. Para renombrar, procesar o transferir muchos archivos, añade bucles a tu caja de herramientas de shell. El formato genérico de un bucle en bash es:
Pongámoslo en práctica. Imagina que tienes 1000 archivos con espacios en su nombre y quieres sustituir cada espacio por un guion bajo "_".
replace_source=' 'replace_target='_'forfilenamein ./*.csv;donew_filename=${filename//$replace_source/$replace_target}mv"$filename""$new_filename"doneCon la tecnología de
En este ejemplo:
primero declaras dos variables, replace_source y replace_target, para el espacio y el guion bajo;
recorres todos los archivos *.csv de la carpeta actual;
para cada filename, creas un new_filename sustituyendo cada espacio por un guion bajo;
y renombras el archivo del nombre actual al nuevo con mv.
De hecho, no solo has usado shell para iterar por archivos en un directorio, sino que también has creado variables.
Variables
Terminamos esta introducción a los comandos de shell con variables. Crear variables en shell es tan simple como:
Escribir bucles y usar variables abre la puerta a manipulaciones de archivos más complejas y es la antesala del scripting en shell, que va más allá de este artículo introductorio. Aun así, el scripting en shell es accesible si empiezas poco a poco y vas ganando confianza.
Conclusión
Las líneas de comando de shell son tremendamente útiles en tu día a día como profesional de datos. Hay muchos más ejemplos y casos de uso por explorar. Y, como verás, casi siempre hay distintas formas de lograr un mismo resultado combinando comandos y opciones. En esos casos, mantenerlo simple suele ser la mejor estrategia.
Ojalá estos ejemplos te ayuden a integrar los comandos de shell en tu kit de procesamiento de datos. Si te apetece, comparte tus trucos de shell conmigo en Twitter: @alexip.
Una lista de las habilidades imprescindibles que todo científico de datos debería tener en su caja de herramientas, incluidos recursos para desarrollar tus habilidades.
Conozca las herramientas esenciales de Python para el desarrollo de software, raspado y desarrollo web, análisis y visualización de datos y aprendizaje automático.
Hay muchas situaciones en las que necesitas analizar, modificar y procesar rápidamente gran cantidad de archivos, tanto en número como en tamaño. A menudo, además, son archivos de texto en formato CSV (valores separados por comas). Abrirlos con hojas de cálculo como Excel, LibreOffice u OpenOffice satura la memoria del equipo. Procesar muchos archivos en un solo lote también suele fallar tras unas horas por alguna anomalía inesperada.
Al final acabas perdiendo tiempo entre bloqueos, reinicios y esperas eternas.
Sin embargo, la mayoría de estas tareas podrían resolverse con unas pocas líneas de código. Es más, dominar unas cuantas órdenes básicas de shell puede ahorrarte mucho tiempo y frustración.
En este post te presento un resumen de varios comandos de shell que uso casi a diario. Espero que te resulten útiles.
Notas rápidas
Ten en cuenta que existen diferentes tipos de shell (bash, zsh, ...). Bash es el más común, ya que es el shell predeterminado en OS X y en las principales distribuciones de Linux. Zsh (y Oh My Zsh) es una alternativa muy popular y potente. Los comandos de shell incluidos en este artículo se han probado en bash sobre OS X (macOS) y deberían funcionar en otros shells y entornos.
Todos los ejemplos siguientes se basan en el conjunto de datos adult del repositorio de UCI Machine Learning, también conocido como "Census Income" y disponible aquí. Suele emplearse para predecir si los ingresos superan los \$50K/año a partir de datos censales. Con 48.842 filas y 14 atributos no es un dataset grande, pero es suficiente para ilustrar los ejemplos. Aunque los datos se guardan con la extensión
.data, es un CSV bien formateado. ¡Descárgalo si quieres seguir el tutorial paso a paso!Cuenta con
wcAnte un archivo de texto nuevo, quizá quieras saber cuántas líneas tiene. Puedes hacerlo con el comando de conteo
wc -l:Esto indica que el archivo
adult.datacontiene 32.562 filas. La opción-lle dice awcque cuente líneas. También puedes usarwcpara contar palabras con la opción-w.El archivo
adult.datacontiene casi 500k palabras.El comando
wctambién puede contar el número de archivos en un directorio usando como entrada la salida de un simplels -l. Usar la salida de un comando como entrada de otro mediante la tubería|es un patrón muy útil llamado pipelining. Lo verás varias veces en este post.Imagina ahora que tienes una carpeta con muchos archivos. El siguiente comando te dirá exactamente cuántos contiene:
Hay muchas más aplicaciones de
wcsi empiezas a usar comodines y subcarpetas.Volvamos a tu archivo
adult.datay mira las primeras líneas conhead. Por defecto,headmuestra 10 líneas; puedes limitar la salida a las 2 primeras con la opción-n.Y verás que el archivo no tiene fila de cabecera: los nombres de columna no están incluidos. Puedes añadirla concatenando dos archivos con el comando
cat.Concatena archivos con
catEl comando
catimprime el contenido de un archivo en la salida estándar (tu terminal). También sirve para concatenar varios archivos. El comandocat file_1.csv file_2.csv > target_file.csvfusionará el contenido defile_1.csvyfile_2.csventarget_file.csv, añadiendofile_2.csval final defile_1.csv.El archivo de cabecera no está en el dataset original, así que tienes que crearlo. Para ello, haz
echode la lista de nombres de columna separados por comas en un archivoheader.csv.En este ejemplo, usaste la redirección de shell
>para volcar la salida decatal archivoadult.csv. El símbolo>crea el archivo o reemplaza por completo su contenido si ya existe. Si lo duplicas>>, se añadirá el nuevo contenido al final sin borrar lo anterior.Ahora añade el archivo
header.csval principio deadult.data. De paso, renombraadult.dataaadult.csv, ya que es, al fin y al cabo, un CSV.Comprueba que la primera fila de
adult.csvcontiene los nombres de columna con:Modifica un archivo con
sedOtro caso típico de minería de datos ocurre cuando un archivo está corrupto o mal formateado, por ejemplo con caracteres no UTF-8 o comas fuera de sitio. Puedes corregirlo sin abrirlo con el comando
sed.El patrón genérico de
sedesEl dataset
adult.csvusa el carácter?para indicar valores ausentes. Puedes sustituirlo por un valor por defecto mejor usandosed. La cadena vacía es preferible para marcar valores ausentes, ya que se interpretará comoNaNal cargar los datos en un DataFrame de Pandas.Esto te da un recuento de 2.399 líneas con al menos una columna con un valor ausente indicado por
?. El siguiente comando sustituirá todas las columnas con?por una cadena vacía. Todas las celdas que solo contienen?seguido de un espacio quedarán realmente vacías.Fíjate en que usas el delimitador de columna
,en las cadenas origen y destino para evitar reemplazar signos de interrogación válidos que puedan aparecer en otras partes del dataset.Crear un subconjunto de un archivo grande
Imagina ahora que trabajas con un archivo de más de 30 millones de filas. Mientras preparas tu script en Python o R, querrás probar el flujo con una muestra del archivo original y no cargar todo en memoria. Puedes combinar
headytailpara crear una submuestra del dataset original.headmuestra la primera parte de los archivos;tailmuestra la última parteComo ya viste, estos comandos aceptan la opción
-n(de--lines) para limitar el número de líneas en la salida.tailtambién acepta-f(de--follow) para mostrar en tiempo real los datos que se van añadiendo al final del archivo. Es muy útil para monitorizar logs mientras se ejecutan tus scripts contail -f <logfile>.Combinando la tubería
|conheadytail, puedes extraer un número concreto de líneas de un archivo y exportarlas a un archivo de subconjunto. Por ejemplo, para extraer 20 líneas empezando en la línea 100:Primero tomas con
headfirst_line + number_of_lines y luego aplicastailpara quedarte con number_of_lines. El comando genérico es:donde
total_lines = first_line + number_of_lines. Eso sí, tu muestra ya no tiene la fila de cabecera. Sabemos añadirla de nuevo en el subconjunto concatconcatenando el archivo de muestra y elheader.csvque creamos antes:Fíjate en que no usaste el nombre fuente
adult\_sample.csvcomo nombre de destino, sino que creasteadult\_sample\_with\_header.csv. Usarcatcon un destino que coincide con una de las fuentes puede producir resultados inesperados. Mejor crea un archivo nuevo como salida decat.Encontrar duplicados con
uniqCon el comando
uniqpuedes detectar líneas repetidas adyacentes en un archivo.uniqtiene varias opciones útiles:uniq -c: añade el recuento de repeticiones a cada línea;uniq -d: muestra solo las líneas duplicadas; yuniq -u: muestra solo las líneas únicas.Eso sí,
uniqno es "listo": no detecta repeticiones si no son adyacentes. Por tanto, primero hay quesortel archivo. Este comando cuenta el número de líneas duplicadas enadult.csv:y muestra que hay 23 duplicados. El siguiente comando toma todas las líneas con su recuento, ordena en reversa y muestra los 3 duplicados principales:
Hay muchas combinaciones potentes si mezclas
sortyuniqcon distintas opciones. Usaman sortyman uniqpara explorar más.Seleccionar columnas con
cutLo mejor de los CSV y los comandos de shell es que puedes trabajar a nivel de columna usando
cutpara seleccionar la que te interese.cutacepta dos opciones principales:-dpara indicar el delimitador de columnas y-fpara especificar las columnas. En el siguiente ejemplo, usascutpara conocer el número de valores únicos que toma la variable categóricaworkclass(columna 2).Primero selecciona la columna
workclassy encadena aheadpara verificar que es la correcta:Ahora, para contar únicos, ordena la salida de
cuty encadénala auniq -casí:Esto te indica, por ejemplo, que hay 1.837 valores nulos y que la clase mayoritaria es
Privatecon 22.969 apariciones.La línea anterior es similar al método
value_counts()aplicado a una Serie con los datos deadult.csv.Bucles
Hasta ahora, has trabajado sobre un único archivo. Para renombrar, procesar o transferir muchos archivos, añade bucles a tu caja de herramientas de shell. El formato genérico de un bucle en bash es:
Pongámoslo en práctica. Imagina que tienes 1000 archivos con espacios en su nombre y quieres sustituir cada espacio por un guion bajo "_".
En este ejemplo:
replace_sourceyreplace_target, para el espacio y el guion bajo;*.csvde la carpeta actual;filename, creas unnew_filenamesustituyendo cada espacio por un guion bajo;mv.De hecho, no solo has usado shell para iterar por archivos en un directorio, sino que también has creado variables.
Variables
Terminamos esta introducción a los comandos de shell con variables. Crear variables en shell es tan simple como:
Por ejemplo:
Observa que no hay espacios alrededor del signo '='.
var = '<a string>'no funcionaría. Para ver el valor, simplemente hazecho:Y para usarla en un script, enciérrala entre comillas como ya viste:
Escribir bucles y usar variables abre la puerta a manipulaciones de archivos más complejas y es la antesala del scripting en shell, que va más allá de este artículo introductorio. Aun así, el scripting en shell es accesible si empiezas poco a poco y vas ganando confianza.
Conclusión
Las líneas de comando de shell son tremendamente útiles en tu día a día como profesional de datos. Hay muchos más ejemplos y casos de uso por explorar. Y, como verás, casi siempre hay distintas formas de lograr un mismo resultado combinando comandos y opciones. En esos casos, mantenerlo simple suele ser la mejor estrategia.
Ojalá estos ejemplos te ayuden a integrar los comandos de shell en tu kit de procesamiento de datos. Si te apetece, comparte tus trucos de shell conmigo en Twitter: @alexip.
Echa un vistazo a nuestro tutorial Configura un entorno de data science en tu ordenador.