Как мне импортировать CSV в Pandas df, где данные организованы по столбцу индекса с отношениями родитель / потомок? - PullRequest
0 голосов
/ 07 февраля 2019

У меня есть ГБ данных в этом текстовом формате:

1,'Acct01','Freds Autoshop'   
2,'3-way-Cntrl','Y'   
1000,576,686,837   
1001,683,170,775   
1,'Acct02','Daves Tacos'   
2,'centrifugal','N'   
1000,334,787,143   
1001,749,132,987

В первом столбце указано содержимое строки, и это серия индексов, которая повторяется для каждой учетной записи (Acct01, Acct02...).Строки со значениями индекса (1,2) взаимно однозначно связаны с каждой учетной записью (родительской).Я хотел бы объединить эти данные в фрейм данных, который связывает данные уровня учетной записи (индекс = 1,2) с соответствующими данными ряда (1000, 10001, 1002, 1003 ...) дочерних данных в виде плоской df.

Желаемый д.ф .:

'Acct01','Freds Autoshop','3-way-Cntrl','Y',1000,576,686,837   
'Acct01','Freds Autoshop','3-way-Cntrl','Y',1001,683,170,775   
'Acct02','Daves Tacos',2,'centrifugal','N',1000,334,787,143   
'Acct02','Daves Tacos',2,'centrifugal','N',1001,749,132,987   

Я смог сделать это очень механическим, очень медленным построчным процессом:

import pandas as pd
import numpy as np
import time

file = 'C:\\PythonData\\AcctData.txt'

t0 = time.time()

pdata = [] # Parse data
acct = []  # Account Data
row = {}   #Assembly Container

#Set dataframe columns
df = pd.DataFrame(columns=['Account','Name','Type','Flag','Counter','CNT01','CNT02','CNT03'])

# open the file and read through it line by line
with open(file, 'r') as f:
    for line in f:

        #Strip each line
        pdata = [x.strip() for x in line.split(',')]

        #Use the index to parse data into either acct[] for use on the rows with counter > 2
        indx = int(pdata[0])

        if indx == 1:
            acct.clear()
            acct.append(pdata[1])
            acct.append(pdata[2])
        elif indx == 2:
            acct.append(pdata[1])
            acct.append(pdata[2])
        else:
            row.clear()
            row['Account'] = acct[0]
            row['Name'] = acct[1]
            row['Type'] = acct[2]
            row['Flag'] = acct[3]
            row['Counter'] = pdata[0]
            row['CNT01'] = pdata[1]
            row['CNT02'] = pdata[2]
            row['CNT03'] = pdata[3]

        if indx > 2:
            #data.append(row)
            df = df.append(row, ignore_index=True)

t1 = time.time()

totalTimeDf = t1-t0

TTDf = '%.3f'%(totalTimeDf)
print(TTDf + " Seconds to Complete df: " + i_filepath)

print(df) 

Результат:

0.018 Seconds to Complete df: C:\PythonData\AcctData.txt   
    Account              Name             Type Flag Counter CNT01 CNT02 CNT03   
0  'Acct01'  'Freds Autoshop'    '3-way-Cntrl'  'Y'    1000   576   686   837   
1  'Acct01'  'Freds Autoshop'    '3-way-Cntrl'  'Y'    1001   683   170   775   
2  'Acct02'     'Daves Tacos'  'centrifugal'  'N'    1000   334   787   143   
3  'Acct02'     'Daves Tacos'  'centrifugal'  'N'    1001   749   132   987   

Это работает, но трагически медленно.Я подозреваю, что есть очень простой питонный способ импортировать и организовывать в df.Похоже, OrderDict будет правильно организовать данные следующим образом:

import csv
from collections import OrderedDict

od = OrderedDict()

file_name = 'C:\\PythonData\\AcctData.txt'
try:
    csvfile = open(file_name, 'rt')
except:
    print("File not found")
csvReader = csv.reader(csvfile, delimiter=",")

for row in csvReader:
    key = row[0]
    od.setdefault(key,[]).append(row)   
od

Результат:

OrderedDict([('1',   
[['1', "'Acct01'", "'Freds Autoshop'"],   
['1', "'Acct02'", "'Daves Tacos'"]]),   
('2',   
[['2', "'3-way-Cntrl'", "'Y'"],   
['2', "'centrifugal'", "'N'"]]),   
('1000',   
[['1000', '576', '686', '837'], ['1000', '334', '787', '143']]),   
('1001',   
[['1001', '683', '170', '775'], ['1001', '749', '132', '987']])]) 

Из OrderDict я не смог выяснитькак объединить ключи 1,2 и связать с определенной серией ключей acct (1000, 1001), а затем добавить в df.Как перейти от OrderedDict к df при выравнивании данных Parent / Child?Или есть лучший способ обработать эти данные?

1 Ответ

0 голосов
/ 07 февраля 2019

Я не уверен, является ли это постом или питоническим способом, но я полагаю, что подход панды мог бы подойти, так как вам нужно выполнять итерацию для каждых 4 строк странным реальным специфическим способом:

первый импортбиблиотеки для работы:

import pandas as pd
import numpy as np

, так как у меня не было файла для загрузки, я просто воссоздал его как массив ( этой части вам придется проделать некоторую работу или просто загрузитьэто будет хорошо для DataFrame панды с 4 столбцами [как на следующем шаге] ):

data = [[1,'Acct01','Freds Autoshop'],   
[2,'3-way-Cntrl','Y'  ], 
[1000,576,686,837   ],
[1001,683,170,775   ],
[1002,333,44,885   ],
[1003,611183,12,1   ],
[1,'Acct02','Daves Tacos'   ],
[2,'centrifugal','N'   ],
[1000,334,787,143  ] ,
[1001,749,132,987],
[1,'Acct03','Norah Jones'   ],
[2,'undertaker','N'   ],
[1000,323,1,3  ] ,
[1001,311,2,111  ] ,
[1002,95,112,4]]

Создан фрейм данных с вышеуказанными данными + создан новый столбец с numpy'snans (быстрее, чем panda) в качестве заполнителей.

df = pd.DataFrame(data)
df['4']= np.nan
df['5']= np.nan
df['6']= np.nan
df['7']= np.nan
df['8']= np.nan
df.columns = ['idx','Account','Name','Type','Flag','Counter','CNT01','CNT02','CNT3']

Создание нового df, который будет получать каждый раз, когда будет отображаться "AcctXXXX" и сколько строк будет ниже до следующего родителя.

# Getting the unique "Acct" and their index position into an array
acct_idx_pos = np.array([df[df['Account'].str.contains('Acct').fillna(False)]['Account'].values, df[df['Account'].str.contains('Acct').fillna(False)].index.values])

# Making a df with the transposed array
df_pos = pd.DataFrame(acct_idx_pos.T, columns=['Acct', 'Position'])

# Shifting the values into a new column and filling the last value (nan) with the df length
df_pos['End_position'] = df_pos['Position'].shift(-1)
df_pos['End_position'][-1:] = len(df)

# Making the column we want, that is the number of loops we'll go
df_pos['Position_length'] = df_pos['End_position'] - df_pos['Position']

AПользовательская функция, которая использует фиктивный Dataframe и объединяет временные (будут использоваться позже)

def concatenate_loop_dfs(df_temp, df_full, axis=0):
    """
    to avoid retyping the same line of code for every df.
    the parameters should be the temporary df created at each loop and the concatenated DF that will contain all
    values which must first be initialized (outside the loop) as df_name = pd.DataFrame(). """      

    if df_full.empty:
        df_full = df_temp
    else:
        df_full = pd.concat([df_full, df_temp], axis=axis)

    return df_full

Создана функция, которая будет зацикливаться для заполнения каждой строки и отбрасывать дублирующиеся строки:

# a complicated loop function
def shorthen_df(df, num_iterations):

    # to not delete original df
    dataframe = df.copy()

    # for the slicing, we need to start at the first row.
    curr_row = 1

    # fill current row's nan values with values from next row
    dataframe.iloc[curr_row-1:curr_row:,3] = dataframe.iloc[curr_row:curr_row+1:,1].values
    dataframe.iloc[curr_row-1:curr_row:,4] = dataframe.iloc[curr_row:curr_row+1:,2].values
    dataframe.iloc[curr_row-1:curr_row:,5] = dataframe.iloc[curr_row+1:curr_row+2:,0].values
    dataframe.iloc[curr_row-1:curr_row:,6] = dataframe.iloc[curr_row+1:curr_row+2:,1].values
    dataframe.iloc[curr_row-1:curr_row:,7] = dataframe.iloc[curr_row+1:curr_row+2:,2].values
    dataframe.iloc[curr_row-1:curr_row:,8] = dataframe.iloc[curr_row+1:curr_row+2:,3].values

    # the "num_iterations-2" is because the first two lines are filled and not replaced 
    # as the next ones will be. So this will vary correctly to each "account"
    for i in range(1, num_iterations-2):
        # Replaces next row with values from previous row
        dataframe.iloc[curr_row+(i-1):curr_row+i:] = dataframe.iloc[curr_row+(i-2):curr_row+(i-1):].values
        dataframe.iloc[curr_row+(i-1):curr_row+i:,5] = dataframe.iloc[curr_row+i+1:curr_row+i+2:,0].values
        dataframe.iloc[curr_row+(i-1):curr_row+i:,6] = dataframe.iloc[curr_row+i+1:curr_row+i+2:,1].values
        dataframe.iloc[curr_row+(i-1):curr_row+i:,7] = dataframe.iloc[curr_row+i+1:curr_row+i+2:,2].values
        dataframe.iloc[curr_row+(i-1):curr_row+i:,8] = dataframe.iloc[curr_row+i+1:curr_row+i+2:,3].values

    # last 2 rows of df
    dataframe = dataframe[0:len(dataframe)-2]

    return dataframe

Наконец , креатинg фиктивный DF, который объединит все "Acct" и зациклит для каждого с его положением, используя обе функции выше.

df_final= pd.DataFrame()
for start, end, iterations in zip(df_pos.Position.values, df_pos.End_position.values, df_pos.Position_length.values):
    df2 = df[start:end]

    df_temp = shorthen_df(df2, iterations)

    df_final = concatenate_loop_dfs(df_temp, df_final)

# Dropping first/unnecessary columns
df_final.drop('idx', axis=1, inplace=True)


# resetting index
df_final.reset_index(inplace=True, drop=True)
df_final

возвращает

    Account Name    Type    Flag    Counter CNT01   CNT02   CNT3
0   Acct01  Freds Autoshop  3-way-Cntrl Y   1000.0  576 686 837
1   Acct01  Freds Autoshop  3-way-Cntrl Y   1001.0  683 170 775
2   Acct01  Freds Autoshop  3-way-Cntrl Y   1002.0  333 44  885
3   Acct01  Freds Autoshop  3-way-Cntrl Y   1003.0  611183  12  1
4   Acct02  Daves Tacos centrifugal N   1000.0  334 787 143
5   Acct02  Daves Tacos centrifugal N   1001.0  749 132 987
6   Acct03  Norah Jones undertaker  N   1000.0  323 1   3
7   Acct03  Norah Jones undertaker  N   1001.0  311 2   111
8   Acct03  Norah Jones undertaker  N   1002.0  95  112 4
...