¿Cómo se crea un dict anidado en Python?

149

Tengo 2 archivos CSV: 'Datos' y 'Mapeo':

  • Archivo 'Mapping' tiene 4 columnas: Device_Name, GDN, Device_Type, y Device_OS. Las cuatro columnas están pobladas.
  • El archivo 'Datos' tiene estas mismas columnas, con la Device_Namecolumna poblada y las otras tres columnas en blanco.
  • Quiero que mi código Python para abrir los archivos y para cada uno Device_Nameen el archivo de datos, mapa su GDN, Device_Typey Device_OSel valor del archivo de asignación.

Sé cómo usar dict cuando solo hay 2 columnas presentes (se necesita 1 para mapear) pero no sé cómo lograr esto cuando se necesitan mapear 3 columnas.

A continuación se muestra el código con el que intenté realizar la asignación de Device_Type:

x = dict([])
with open("Pricing Mapping_2013-04-22.csv", "rb") as in_file1:
    file_map = csv.reader(in_file1, delimiter=',')
    for row in file_map:
       typemap = [row[0],row[2]]
       x.append(typemap)

with open("Pricing_Updated_Cleaned.csv", "rb") as in_file2, open("Data Scraper_GDN.csv", "wb") as out_file:
    writer = csv.writer(out_file, delimiter=',')
    for row in csv.reader(in_file2, delimiter=','):
         try:
              row[27] = x[row[11]]
         except KeyError:
              row[27] = ""
         writer.writerow(row)

Vuelve Attribute Error.

Después de investigar un poco, creo que necesito crear un dict anidado, pero no tengo idea de cómo hacerlo.

atams
fuente
Device_NameLa columna es la clave en ambos archivos, en esta clave quiero asignar los valores Device_OS, GDN y Device_Type del archivo de asignación al archivo de datos.
Atams
¿Quieres poder hacer algo como row[27] = x[row[11]]["Device_OS"]?
Janne Karila
Esto no necesita un dict anidado, necesariamente. Puede usar pandas, read_csv, hacer Device_Nameel índice, luego puede directamente joinlos dos marcos de datos en su índice Device_Name.
smci

Respuestas:

307

Un dict anidado es un diccionario dentro de un diccionario. Una cosa muy simple.

>>> d = {}
>>> d['dict1'] = {}
>>> d['dict1']['innerkey'] = 'value'
>>> d
{'dict1': {'innerkey': 'value'}}

También puede usar a defaultdictdesde el collectionspaquete para facilitar la creación de diccionarios anidados.

>>> import collections
>>> d = collections.defaultdict(dict)
>>> d['dict1']['innerkey'] = 'value'
>>> d  # currently a defaultdict type
defaultdict(<type 'dict'>, {'dict1': {'innerkey': 'value'}})
>>> dict(d)  # but is exactly like a normal dictionary.
{'dict1': {'innerkey': 'value'}}

Puedes llenar eso como quieras.

Recomendaría en su código algo como lo siguiente:

d = {}  # can use defaultdict(dict) instead

for row in file_map:
    # derive row key from something 
    # when using defaultdict, we can skip the next step creating a dictionary on row_key
    d[row_key] = {} 
    for idx, col in enumerate(row):
        d[row_key][idx] = col

De acuerdo a tu comentario :

puede estar por encima del código está confundiendo la pregunta. Mi problema en pocas palabras: tengo 2 archivos a.csv b.csv, a.csv tiene 4 columnas ijkl, b.csv también tiene estas columnas. Es una especie de columnas clave para estos csvs '. La columna jkl está vacía en a.csv pero se rellena en b.csv. Quiero asignar valores de columnas jk l usando 'i' como columna clave de b.csv a un archivo a.csv

Mi sugerencia sería algo como esto (sin usar defaultdict):

a_file = "path/to/a.csv"
b_file = "path/to/b.csv"

# read from file a.csv
with open(a_file) as f:
    # skip headers
    f.next()
    # get first colum as keys
    keys = (line.split(',')[0] for line in f) 

# create empty dictionary:
d = {}

# read from file b.csv
with open(b_file) as f:
    # gather headers except first key header
    headers = f.next().split(',')[1:]
    # iterate lines
    for line in f:
        # gather the colums
        cols = line.strip().split(',')
        # check to make sure this key should be mapped.
        if cols[0] not in keys:
            continue
        # add key to dict
        d[cols[0]] = dict(
            # inner keys are the header names, values are columns
            (headers[idx], v) for idx, v in enumerate(cols[1:]))

Sin embargo, tenga en cuenta que para analizar archivos csv hay un módulo csv .

Inbar Rose
fuente
puede estar por encima del código está confundiendo la pregunta. Mi problema en pocas palabras: tengo 2 archivos a.csv b.csv, a.csvtiene 4 columnas i j k l, b.csvtambién tiene estas columnas. ies una especie de columnas clave para estos csvs '. j k lla columna está vacía a.csvpero llena en b.csv. Quiero asignar valores de j k lcolumnas usando 'i` como columna clave de b.csv a un archivo a.csv.
atams
64

ACTUALIZACIÓN : para una longitud arbitraria de un diccionario anidado, vaya a esta respuesta .

Use la función defaultdict de las colecciones.

Alto rendimiento: "si la clave no está en dict" es muy costoso cuando el conjunto de datos es grande.

Bajo mantenimiento: haga que el código sea más legible y se pueda ampliar fácilmente.

from collections import defaultdict

target_dict = defaultdict(dict)
target_dict[key1][key2] = val
Junchen
fuente
3
from collections import defaultdict target_dict = defaultdict(dict) target_dict['1']['2']me datarget_dict['1']['2'] KeyError: '2'
hackear
1
debe asignar un valor antes de obtenerlo.
Junchen
24

Para niveles arbitrarios de anidamiento:

In [2]: def nested_dict():
   ...:     return collections.defaultdict(nested_dict)
   ...:

In [3]: a = nested_dict()

In [4]: a
Out[4]: defaultdict(<function __main__.nested_dict>, {})

In [5]: a['a']['b']['c'] = 1

In [6]: a
Out[6]:
defaultdict(<function __main__.nested_dict>,
            {'a': defaultdict(<function __main__.nested_dict>,
                         {'b': defaultdict(<function __main__.nested_dict>,
                                      {'c': 1})})})
Andrés
fuente
2
Lo que hace la respuesta anterior con una función de dos líneas, también puede hacerlo con una lambda de una línea, como en esta respuesta .
Acumenus
3

Es importante recordar al usar defaultdict y módulos dict anidados similares como nested_dict, que buscar una clave inexistente puede crear inadvertidamente una nueva entrada de clave en el dict y causar muchos estragos.

Aquí hay un ejemplo de Python3 con nested_dictmódulo:

import nested_dict as nd
nest = nd.nested_dict()
nest['outer1']['inner1'] = 'v11'
nest['outer1']['inner2'] = 'v12'
print('original nested dict: \n', nest)
try:
    nest['outer1']['wrong_key1']
except KeyError as e:
    print('exception missing key', e)
print('nested dict after lookup with missing key.  no exception raised:\n', nest)

# Instead, convert back to normal dict...
nest_d = nest.to_dict(nest)
try:
    print('converted to normal dict. Trying to lookup Wrong_key2')
    nest_d['outer1']['wrong_key2']
except KeyError as e:
    print('exception missing key', e)
else:
    print(' no exception raised:\n')

# ...or use dict.keys to check if key in nested dict
print('checking with dict.keys')
print(list(nest['outer1'].keys()))
if 'wrong_key3' in list(nest.keys()):

    print('found wrong_key3')
else:
    print(' did not find wrong_key3')

Salida es:

original nested dict:   {"outer1": {"inner2": "v12", "inner1": "v11"}}

nested dict after lookup with missing key.  no exception raised:  
{"outer1": {"wrong_key1": {}, "inner2": "v12", "inner1": "v11"}} 

converted to normal dict. 
Trying to lookup Wrong_key2 

exception missing key 'wrong_key2' 

checking with dict.keys 

['wrong_key1', 'inner2', 'inner1']  
did not find wrong_key3
Skysail
fuente