2016-08-19 2 views
1

Я очень новичок в Python/JSON, поэтому, пожалуйста, несите меня на этом. Я мог бы сделать это в R, но нам нужно использовать Python, чтобы преобразовать его в Python/Spark/MongoDB. Кроме того, я просто размещаю минимальное подмножество - у меня есть еще несколько типов файлов, поэтому, если кто-нибудь может мне помочь, я могу использовать это для интеграции большего количества файлов и типов файлов:Python: объединить два CSV-файла в многоуровневые JSON

Возвращаясь к моей проблеме:

У меня есть два входных файла tsv, которые мне нужно объединить и конвертировать в JSON. Оба файла содержат столбцы с именами и образцами, а также некоторые дополнительные столбцы. Однако gene и sample могут или не совпадать, как я показал - f2.tsv имеет все гены в f1.tsv, но также имеет дополнительный ген g3. Аналогично, оба файла имеют перекрывающиеся, а также неперекрывающиеся значения в столбце sample.

# f1.tsv – has gene, sample and additional column other1 

$ cat f1.tsv 
gene sample other1 
g1  s1  a1 
g1  s2  b1 
g1  s3a  c1 
g2  s4  d1 

# f2.tsv – has gene, sample and additional columns other21, other22 

$ cat f2.tsv 
gene sample other21 other22 
g1  s1  a21  a22 
g1  s2  b21  b22 
g1  s3b  c21  c22 
g2  s4  d21  d22 
g3  s5  f21  f22 

Ген образует верхний уровень, каждый ген имеет несколько образцов, которые образуют второй уровень и дополнительные столбцы образуют extras, который является третьим уровнем. Дополнительные параметры разделены на два, потому что один файл имеет other1, а второй файл имеет other21 и other22. Другие файлы, которые я буду включать позже, будут иметь другие поля, такие как other31 и other32 и т. Д., Но они все равно будут содержать столбцы генов и образцов.

# expected output – JSON by combining both tsv files. 
$ cat output.json 
[{ 
    "gene":"g1", 
    "samples":[ 
    { 
     "sample":"s2", 
     "extras":[ 
     { 
      "other1":"b1" 
     }, 
     { 
      "other21":"b21", 
      "other22":"b22" 
     } 
     ] 
    }, 
    { 
     "sample":"s1", 
     "extras":[ 
     { 
      "other1":"a1" 
     }, 
     { 
      "other21":"a21", 
      "other22":"a22" 
     } 
     ] 
    }, 
    { 
     "sample":"s3b", 
     "extras":[ 
     { 
      "other21":"c21", 
      "other22":"c22" 
     } 
     ] 
    }, 
    { 
     "sample":"s3a", 
     "extras":[ 
     { 
      "other1":"c1" 
     } 
     ] 
    } 
    ] 
},{ 
    "gene":"g2", 
    "samples":[ 
    { 
     "sample":"s4", 
     "extras":[ 
     { 
      "other1":"d1" 
     }, 
     { 
      "other21":"d21", 
      "other22":"d22" 
     } 
     ] 
    } 
    ] 
},{ 
    "gene":"g3", 
    "samples":[ 
    { 
     "sample":"s5", 
     "extras":[ 
     { 
      "other21":"f21", 
      "other22":"f22" 
     } 
     ] 
    } 
    ] 
}] 

Как преобразовать два csv-файла в одноразрядный JSON на основе двух общих столбцов?

Я бы очень признателен за любую помощь, которую я могу получить от этого.

Спасибо!

ответ

2

Вот еще один вариант. Я попытался упростить управление, когда вы начинаете добавлять больше файлов. Вы можете запустить в командной строке и предоставить аргументы, по одному для каждого файла, который вы хотите добавить. Имена генов/образцов хранятся в словарях для повышения эффективности. Форматирование вашего желаемого объекта JSON выполняется в каждом методе format() класса. Надеюсь это поможет.

import csv, json, sys 

class Sample(object): 
    def __init__(self, name, extras): 
     self.name = name 
     self.extras = [extras] 

    def format(self): 
     map = {} 
     map['sample'] = self.name 
     map['extras'] = self.extras 
     return map 

    def add_extras(self, extras): 
     #edit 8/20 
     #always just add the new extras to the list 
     for extra in extras: 
      self.extras.append(extra) 

class Gene(object): 
    def __init__(self, name, samples): 
     self.name = name 
     self.samples = samples 

    def format(self): 
     map = {} 
     map ['gene'] = self.name 
     map['samples'] = sorted([self.samples[sample_key].format() for sample_key in self.samples], key=lambda sample: sample['sample']) 
     return map 

    def create_or_add_samples(self, new_samples): 
     # loop through new samples, seeing if they already exist in the gene object 
     for sample_name in new_samples: 
      sample = new_samples[sample_name] 
      if sample.name in self.samples: 
       self.samples[sample.name].add_extras(sample.extras) 
      else: 
       self.samples[sample.name] = sample 

class Genes(object): 
    def __init__(self): 
     self.genes = {} 

    def format(self): 
     return sorted([self.genes[gene_name].format() for gene_name in self.genes], key=lambda gene: gene['gene']) 

    def create_or_add_gene(self, gene): 
     if not gene.name in self.genes: 
      self.genes[gene.name] = gene 
     else: 
      self.genes[gene.name].create_or_add_samples(gene.samples) 

def row_to_gene(headers, row): 
    gene_name = "" 
    sample_name = "" 
    extras = {} 
    for value in enumerate(row): 
     if headers[value[0]] == "gene": 
      gene_name = value[1] 
     elif headers[value[0]] == "sample": 
      sample_name = value[1] 
     else: 
      extras[headers[value[0]]] = value[1] 
    sample_dict = {} 
    sample_dict[sample_name] = Sample(sample_name, extras) 
    return Gene(gene_name, sample_dict) 

if __name__ == '__main__': 
    delim = "\t" 
    genes = Genes() 
    files = sys.argv[1:] 

    for file in files: 
     print("Reading " + str(file)) 
     with open(file,'r') as f1: 
      reader = csv.reader(f1, delimiter=delim) 
      headers = [] 
      for row in reader: 
       if len(headers) == 0: 
        headers = row 
       else: 
        genes.create_or_add_gene(row_to_gene(headers, row)) 

    result = json.dumps(genes.format(), indent=4) 
    print(result) 
    with open('json_output.txt', 'w') as output: 
     output.write(result) 
+0

Это отлично работает - мне очень нравится, что у вас есть такое обобщение - где я могу указать разделитель, а также любое количество файлов. Это невероятно! –

+0

я только есть одна проблема - для g1/s1 это показывает '' ' "дополнительные услуги": [ { "Разное1": "a1" }, [ { "other22": "A22", «other21 ":" a21 " } ] ]' '' Я хочу удалить внутренние квадратные скобки из дополнительных функций. –

+0

@ KomalRathi oops, извините за это. Я отредактировал с исправлением – gregbert

2

Это похоже на проблему для pandas! К сожалению, панды только забирают нас до сих пор, и мы тогда должны сделать некоторые манипуляции самостоятельно. Это не быстрый и не особенно эффективный код, но он выполнит свою работу.

import pandas as pd 
import json 
from collections import defaultdict 

# here we import the tsv files as pandas df 
f1 = pd.read_table('f1.tsv', delim_whitespace=True) 
f2 = pd.read_table('f2.tsv', delim_whitespace=True) 

# we then let pandas merge them 
newframe = f1.merge(f2, how='outer', on=['gene', 'sample']) 

# have pandas write them out to a json, and then read them back in as a 
# python object (a list of dicts) 
pythonList = json.loads(newframe.to_json(orient='records')) 


newDict = {} 
for d in pythonList: 
    gene = d['gene'] 
    sample = d['sample'] 
    sampleDict = {'sample':sample, 
        'extras':[]} 

    extrasdict = defaultdict(lambda:dict()) 

    if gene not in newDict: 
     newDict[gene] = {'gene':gene, 'samples':[]} 

    for key, value in d.iteritems(): 
     if 'other' not in key or value is None: 
      continue 
     else: 
      id = key.split('other')[-1] 
      if len(id) == 1: 
       extrasdict['1'][key] = value 
      else: 
       extrasdict['{}'.format(id[0])][key] = value 

    for value in extrasdict.values(): 
     sampleDict['extras'].append(value) 

    newDict[gene]['samples'].append(sampleDict) 

newList = [v for k, v in newDict.iteritems()] 

print json.dumps(newList) 

Если это выглядит как решение, которое будет работать для вас, я счастлив провести некоторое время его очистку, чтобы сделать его более удобным для чтения приманки и эффективен.

PS: Если вы любите R, то панды является путь (она была написана, чтобы дать R-подобный интерфейс к данным в питоне)

+0

Это решение идеально подходит! Я только что принял ответ Грегберта, потому что его код имеет функциональность, чтобы указать столько входных файлов и разделителя. Большое спасибо. –

1

Сделайте это шаги:

  1. Читать входящие tsv файлы и агрегировать информацию из разных генов в словарь.
  2. Обработать словарь в соответствии с вашим желаемым форматом.
  3. Запишите результат в файл JSON.

Вот код:

import csv 
import json 
from collections import defaultdict 

input_files = ['f1.tsv', 'f2.tsv'] 
output_file = 'genes.json' 

# Step 1 
gene_dict = defaultdict(lambda: defaultdict(list)) 
for file in input_files: 
    with open(file, 'r') as f: 
     reader = csv.DictReader(f, delimiter='\t') 
     for line in reader: 
      gene = line.pop('gene') 
      sample = line.pop('sample') 
      gene_dict[gene][sample].append(line) 

# Step 2 
out = [{'gene': gene, 
     'samples': [{'sample': sample, 'extras': extras} 
        for sample, extras in samples.items()]} 
     for gene, samples in gene_dict.items()] 

# Step 3 
with open(output_file, 'w') as f: 
    json.dump(out, f) 
+0

Это решение идеально! Я только что принял ответ Грегберта, потому что его код имеет функциональность, чтобы указать столько входных файлов и разделителя. Большое спасибо. –

+0

Обратите внимание, что это также очень легко сделать с моим кодом: чтобы добавить дополнительные входные файлы, добавьте их имена в список 'input_files'; для изменения разделителя, отредактируйте строку 12. –

Смежные вопросы