Более эффективный способ анализа JSON и преобразования в CSV в Python

#python #json #pandas

#python #json #панды

Вопрос:

Я не уверен, что этой информации будет достаточно для предоставления, но в настоящее время я пытаюсь извлечь и отформатировать в csv подмножество данных из очень большого файла, содержащего много объектов JSON в виде строк, и выгрузить его в один csv-файл. У меня есть приведенная ниже реализация. Скорость не так уж и плоха, но мне было интересно, есть ли более эффективный способ сделать это. Я чувствую, что часть pandas, в которой я создаю фреймы данных, может быть немного приятнее:

 for files in zip_files:
    with zipfile.ZipFile(files, 'r') as myzip:
        for logfile in myzip.namelist():
            list1 = []
            list2 = []
            f = myzip.open(logfile)
            contents = f.readlines()
            for line in contents[:]:
                try:
                    parsed = json.loads(line[:-2])
                    if "key1" in parsed.keys():
                        if "val1" in parsed['key1']['key2']:
                            if "val2" in parsed['key3']:
                                list1.append(parsed['key1'])
                                list2.append(parsed['key3'])
                except ValueError as e:
                    pass
                else:
                    pass
            df1 = pd.DataFrame(list1)
            df2 = pd.DataFrame(list2)
            df3 = df2.join(df1)
            df3['col1'] = df3['col1'].apply(lambda x: ','.join([str(i) for i in x]))
            df3 = df3.drop_duplicates()
       with open(csvout, 'a') as f2:
            df.to_csv(f2, header=None, index=False)
            f2.close()
       f.close()
 

Комментарии:

1. Вы могли бы попробовать и использовать read_json and concat , который может быть более эффективным. На данный момент в этом вопросе слишком много всего происходит, на что сложно ответить.

2. Этот вопрос, похоже, не по теме, потому что речь идет об улучшении рабочего кода

3. Спасибо. Не знаком с правилами для вопросов. Есть ли ссылка, которую вы можете предоставить?

Ответ №1:

Я сделал следующее:

  • Аннотировал исходную версию STRANGE (= Я не уверен, что вы делаете), EFFICIENT (= можно сделать более эффективным), SIMPLIFY (= можно упростить).
  • Созданы две другие версии, которые могут быть более эффективными, но изменяют поведение (с точки зрения памяти или поведения выполнения). Это подробно описано ниже.

Чтобы убедиться, что эти предложения действительно полезны, рассмотрите возможность использования ipython %timeit . Введите следующее в приглашении IPython:

 In [0]: %timeit -n<N> %run script.py
 

Где <N> среднее количество запусков (по умолчанию 1000, что может занять слишком много времени).

Аннотированный оригинал

 for files in zip_files:
    with zipfile.ZipFile(files, 'r') as myzip:
        for logfile in myzip.namelist():
            list1 = []
            list2 = []
            f = myzip.open(logfile)

            # contents = f.readlines()
            # for line in contents[:]:
            for line in f: # EFFICIENT: does the same without making a copy

                try:
                    parsed = json.loads(line[:-2])

                    # if "key1" in parsed.keys():
                    if "key1" in parsed: # EFFICIENT: no copy

                        # STRANGE: 'val' in dict checks for key existence by
                        # default, are you sure this is what you want?
                        if "val1" in parsed['key1']['key2']:
                            if "val2" in parsed['key3']:
                                list1.append(parsed['key1'])
                                list2.append(parsed['key3'])
                except ValueError as e:
                    pass
                # STRANGE: Why is this here?
                # else:
                    #     pass
            df1 = pd.DataFrame(list1)
            df2 = pd.DataFrame(list2)
            df3 = df2.join(df1)

            # EFFICIENT: prefer generator over list comprehension
            # df3['col1'] = df3['col1'].apply(lambda x: ','.join([str(i) for i in x]))
            df3['col1'] = df3['col1'].apply(lambda x: ','.join(str(i) for i in x))

            df3.drop_duplicates(inplace=True)

       # SIMPLIFY:
           # with open(csvout, 'a') as f2:
               #     df.to_csv(f2, header=None, index=False)
       #      f2.close()
       # STRANGE: where does `df` come from? Shouldn't this be df3?
       df.to_csv(csvout, mode='a', header=None, index=False)

       # STRANGE: you open f in a loop, but close it outside of the loop?
       f.close()
 

Сборка в памяти, запись один раз

Если у вас достаточно памяти, быстрее может быть следующее: вместо добавления к файлу вы сначала объединяете все файлы в памяти. Это также немного меняет поведение:

  • дубликаты фильтруются по всем файлам

Также некоторые стилистические изменения:

 for files in zip_files:
    with zipfile.ZipFile(files, 'r') as myzip:
        list1, list2 = [], [] # Notice these are outside the loop
        for logfile in myzip.namelist():
            with myzip.open(logfile) as f:
                for line in f:
                    try:
                        parsed = json.loads(line[:-2])
                    except ValueError as e: # Presumably we only wish to catch json value errors
                        pass
                    else:
                        if ("key1" in parsed
                            and "val1" in parsed['key1']['key2']
                            and "val2" in parsed['key3']):
                            list1.append(parsed['key1'])
                            list2.append(parsed['key3'])

        # Write only once
        df = pd.DataFrame(list2).join(pd.DataFrame(list1))
        df['col1'] = df['col1'].apply(lambda x: ','.join(str(i) for i in x))
        df.drop_duplicates(inplace=True)
        df.to_csv(csvout, header=None, index=False)
 

Сборка в памяти, запись один раз, фильтрация дубликатов только для каждого файла

Сохранение локальной фильтрации дубликатов для каждого файла:

 for files in zip_files:
    with zipfile.ZipFile(files, 'r') as myzip:
        dfs = []
        for logfile in myzip.namelist():
            list1, list2 = [], []
            with myzip.open(logfile) as f:
                for line in f:
                    try:
                        parsed = json.loads(line[:-2])
                    except ValueError as e: # Presumably we only wish to catch json value errors
                        pass
                    else:
                        if ("key1" in parsed
                            and "val1" in parsed['key1']['key2']
                            and "val2" in parsed['key3']):
                            list1.append(parsed['key1'])
                            list2.append(parsed['key3'])
            # Build a temporary dataframe to filter the duplicates:
            tmp = pd.DataFrame(list2).join(pd.DataFrame(list1))
            tmp['col1'] = tmp['col1'].apply(lambda x: ','.join(str(i) for i in x))
            tmp.drop_duplicates(inplace=True)
            dfs.append(tmp)

        # Write only once
        pd.concat(dfs, ignore_index=True).to_csv(csvout, header=None, index=False)