Tengo muchos archivos que me gustaría leer en un solo marco de datos de pandas. Un archivo de ejemplo podría verse así:

variable_1_name
variable_2_name
...
variable_n_name
0.0  0.5  0.3  ...  0.8
...
1.0  4.5  6.5  ...  1.0

Entonces, el archivo tiene una lista de nombres de variables (uno por línea) en la parte superior del archivo y luego los datos se presentan en una tabla delimitada por espacios con valores n por fila.

Hay un par de problemas:

1) Hay un número diferente de variables en cada archivo. No todas las variables están presentes en cada archivo.

2) Las variables pueden estar en diferente orden entre los archivos.

¿Cómo puedo leer todos estos datos en un marco de datos de pandas, al tiempo que emparejo los datos correctos entre archivos?

4
Flux Capacitor 31 oct. 2017 a las 00:38

3 respuestas

La mejor respuesta

Ampliando la respuesta de Pal: la mejor manera es leer datos de archivos csv. Entonces, ¿por qué no convertir los archivos a archivos csv (o mejor aún, objetos similares a archivos csv que viven en la memoria) y dejar que pandas haga el trabajo sucio?

try:
    import io  # python3
except ImportError:
    import cStringIO as io  # python2
import pandas as pd

DELIMITER = ','

def pd_read_chunk(file):
    """
    Reads file contents, converts it to a csv file in memory
    and imports a dataframe from it.
    """
    with open(file) as f:
        content = [line.strip() for line in f.readlines()]
        cols = [line for line in content if ' ' not in line]
        vals = [line for line in content if ' ' in line]
        csv_header = DELIMITER.join(cols)
        csv_body = '\n'.join(DELIMITER.join(line.split()) for line in vals)
        stream = io.StringIO(csv_header + '\n' + csv_body)
        return pd.read_csv(stream, sep=DELIMITER)


if __name__ == '__main__':
    files = ('file1', 'file2', )
    # read dataframe from each file and concat all resulting dataframes
    df_chunks = [pd_read_chunk(file) for file in files]
    df = pd.concat(df_chunks)
    print(df)

Si prueba los archivos de muestra de Thom Ives 'respuesta, el script devolverá

     A    B    C    D    E
0  1.0  2.0  3.0  NaN  NaN
1  1.1  2.1  3.1  NaN  NaN
0  NaN  2.2  NaN  4.2  5.2
1  NaN  2.3  NaN  4.3  5.3

Editar : en realidad, no necesitamos el delimitador de coma: podemos reutilizar el espacio como delimitador para poder compactar y acelerar la conversión al mismo tiempo. Aquí hay una versión actualizada de la anterior que tiene menos código y se ejecuta más rápido:

try:
    import io  # python3
except ImportError:
    import cStringIO as io  # python2
import pandas as pd


def pd_read_chunk(file):
    """
    Reads file contents, converts it to a csv file in memory
    and imports a dataframe from it.
    """
    with open(file) as f:
        content = [line.strip() for line in f.readlines()]
        cols = [line for line in content if ' ' not in line]
        vals = [line for line in content if ' ' in line]
        csv_header = ' '.join(cols)
        csv_lines = [csv_header] + vals
        stream = io.StringIO('\n'.join(csv_lines))
        return pd.read_csv(stream, sep=' ')


if __name__ == '__main__':
    files = ('file1', 'file2', )
    # read dataframe from each file and concat all resulting dataframes
    df_chunks = [pd_read_chunk(file) for file in files]
    df = pd.concat(df_chunks)
    print(df)
2
hoefling 31 oct. 2017 a las 11:32

Suponiendo que NO es fácil hacer lo que Pal ha dicho en su buena sugerencia, digamos que tiene dos archivos de datos simplificados:

Data1.txt

A
B
C
1.0 2.0 3.0
1.1 2.1 3.1

Y data2.txt

B
D
E
2.2 4.2 5.2
2.3 4.3 5.3

Use algo como las siguientes dos funciones para 1) obtener los archivos que desea y 2) condicionarlos en marcos de datos de pandas:

import pandas as pd
import os

def Get_Filtered_File_List(topDirectory, checkString = None):
    fileList = []

    fileNamesList = os.listdir(topDirectory)
    for fileName in fileNamesList:
        if checkString == None or checkString in fileName:
            fileList.append(fileName)

    return fileList

def Load_And_Condition_Files_Into_DF(fileList):
    header = []
    arrayOfValues = []
    arrayOfDicts = []

    for file in fileList:
        thisHeader = []
        with open(file,'r') as f:
            arrayOfLines = f.readlines()
            for line in arrayOfLines:

                lineArray = line.split()
                if len(lineArray) == 1:
                    thisHeader.append(lineArray[0])
                else:
                    arrayOfDicts.append({})
                    for i in range(len(lineArray)):
                        arrayOfDicts[-1][thisHeader[i]] = lineArray[i]

            header += thisHeader

    # print arrayOfDicts
    header = sorted(list(set(header)))
    for dict in arrayOfDicts:
        arrayOfValues.append([])
        for name in header:
            try:
                val = dict[name]
                # print '\t', name, val
                arrayOfValues[-1].append(val)
            except:
                # print '\t', name, None
                arrayOfValues[-1].append(None)
    table = [header] + arrayOfValues
    # print table
    return pd.DataFrame(table, columns=table.pop(0))


fileList = Get_Filtered_File_List('./','data')
print Load_And_Condition_Files_Into_DF(fileList)

Qué salidas:

      A    B     C     D     E
0   1.0  2.0   3.0  None  None
1   1.1  2.1   3.1  None  None
2  None  2.2  None   4.2   5.2
3  None  2.3  None   4.3   5.3
1
Thom Ives 30 oct. 2017 a las 23:26

La solución simple sería editar el archivo de texto de la siguiente manera y usar read_csv

variable_1_name, variable_2_name, ..., variable_n_name
0.0  0.5  0.3  ...  0.8
...
1.0  4.5  6.5  ...  1.0

df = pd.read_csv('filename')
1
Pal 30 oct. 2017 a las 22:44