2.8: Diccionarios
- Page ID
- 55106
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\( \newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\)
( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\id}{\mathrm{id}}\)
\( \newcommand{\Span}{\mathrm{span}}\)
\( \newcommand{\kernel}{\mathrm{null}\,}\)
\( \newcommand{\range}{\mathrm{range}\,}\)
\( \newcommand{\RealPart}{\mathrm{Re}}\)
\( \newcommand{\ImaginaryPart}{\mathrm{Im}}\)
\( \newcommand{\Argument}{\mathrm{Arg}}\)
\( \newcommand{\norm}[1]{\| #1 \|}\)
\( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\)
\( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\AA}{\unicode[.8,0]{x212B}}\)
\( \newcommand{\vectorA}[1]{\vec{#1}} % arrow\)
\( \newcommand{\vectorAt}[1]{\vec{\text{#1}}} % arrow\)
\( \newcommand{\vectorB}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vectorC}[1]{\textbf{#1}} \)
\( \newcommand{\vectorD}[1]{\overrightarrow{#1}} \)
\( \newcommand{\vectorDt}[1]{\overrightarrow{\text{#1}}} \)
\( \newcommand{\vectE}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash{\mathbf {#1}}}} \)
\( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \)
\( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)
\(\newcommand{\avec}{\mathbf a}\) \(\newcommand{\bvec}{\mathbf b}\) \(\newcommand{\cvec}{\mathbf c}\) \(\newcommand{\dvec}{\mathbf d}\) \(\newcommand{\dtil}{\widetilde{\mathbf d}}\) \(\newcommand{\evec}{\mathbf e}\) \(\newcommand{\fvec}{\mathbf f}\) \(\newcommand{\nvec}{\mathbf n}\) \(\newcommand{\pvec}{\mathbf p}\) \(\newcommand{\qvec}{\mathbf q}\) \(\newcommand{\svec}{\mathbf s}\) \(\newcommand{\tvec}{\mathbf t}\) \(\newcommand{\uvec}{\mathbf u}\) \(\newcommand{\vvec}{\mathbf v}\) \(\newcommand{\wvec}{\mathbf w}\) \(\newcommand{\xvec}{\mathbf x}\) \(\newcommand{\yvec}{\mathbf y}\) \(\newcommand{\zvec}{\mathbf z}\) \(\newcommand{\rvec}{\mathbf r}\) \(\newcommand{\mvec}{\mathbf m}\) \(\newcommand{\zerovec}{\mathbf 0}\) \(\newcommand{\onevec}{\mathbf 1}\) \(\newcommand{\real}{\mathbb R}\) \(\newcommand{\twovec}[2]{\left[\begin{array}{r}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\ctwovec}[2]{\left[\begin{array}{c}#1 \\ #2 \end{array}\right]}\) \(\newcommand{\threevec}[3]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\cthreevec}[3]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \end{array}\right]}\) \(\newcommand{\fourvec}[4]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\cfourvec}[4]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \end{array}\right]}\) \(\newcommand{\fivevec}[5]{\left[\begin{array}{r}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\cfivevec}[5]{\left[\begin{array}{c}#1 \\ #2 \\ #3 \\ #4 \\ #5 \\ \end{array}\right]}\) \(\newcommand{\mattwo}[4]{\left[\begin{array}{rr}#1 \amp #2 \\ #3 \amp #4 \\ \end{array}\right]}\) \(\newcommand{\laspan}[1]{\text{Span}\{#1\}}\) \(\newcommand{\bcal}{\cal B}\) \(\newcommand{\ccal}{\cal C}\) \(\newcommand{\scal}{\cal S}\) \(\newcommand{\wcal}{\cal W}\) \(\newcommand{\ecal}{\cal E}\) \(\newcommand{\coords}[2]{\left\{#1\right\}_{#2}}\) \(\newcommand{\gray}[1]{\color{gray}{#1}}\) \(\newcommand{\lgray}[1]{\color{lightgray}{#1}}\) \(\newcommand{\rank}{\operatorname{rank}}\) \(\newcommand{\row}{\text{Row}}\) \(\newcommand{\col}{\text{Col}}\) \(\renewcommand{\row}{\text{Row}}\) \(\newcommand{\nul}{\text{Nul}}\) \(\newcommand{\var}{\text{Var}}\) \(\newcommand{\corr}{\text{corr}}\) \(\newcommand{\len}[1]{\left|#1\right|}\) \(\newcommand{\bbar}{\overline{\bvec}}\) \(\newcommand{\bhat}{\widehat{\bvec}}\) \(\newcommand{\bperp}{\bvec^\perp}\) \(\newcommand{\xhat}{\widehat{\xvec}}\) \(\newcommand{\vhat}{\widehat{\vvec}}\) \(\newcommand{\uhat}{\widehat{\uvec}}\) \(\newcommand{\what}{\widehat{\wvec}}\) \(\newcommand{\Sighat}{\widehat{\Sigma}}\) \(\newcommand{\lt}{<}\) \(\newcommand{\gt}{>}\) \(\newcommand{\amp}{&}\) \(\definecolor{fillinmathshade}{gray}{0.9}\)Los diccionarios (a menudo llamados “tablas hash” en otros idiomas) son una forma eficiente e increíblemente útil de asociar datos con otros datos. Considere una lista, que asocia cada elemento de datos de la lista con un entero que comienza en cero:

Un diccionario funciona de la misma manera, excepto que en lugar de índices, los diccionarios usan “claves”, que pueden ser enteros o cadenas. [1] Normalmente dibujaremos diccionarios a lo largo del tiempo, e incluiremos las claves dentro de los corchetes ilustrativos, ya que son tanto una parte de la estructura del diccionario como los valores:

Vamos a crear un diccionario que contenga estas claves y valores con algún código, llamándolo ids_to_gcs
. Tenga en cuenta que este nombre codifica tanto lo que representan las claves como los valores, lo que puede ser útil a la hora de realizar un seguimiento del contenido de estas estructuras. Para crear un diccionario vacío, llamamos a la función dict ()
sin parámetros, que devuelve el diccionario vacío. Entonces, podemos asignar y recuperar valores como lo hacemos con las listas, excepto (1) estaremos usando cadenas como claves en lugar de índices enteros, y (2) podemos asignar valores a claves aunque la clave no esté ya presente.

Entonces podemos acceder a valores individuales al igual que con una lista:

Sin embargo, no podemos acceder a un valor para una clave que no existe. Esto dará como resultado un KeyError
, y el programa se detendrá.

Los diccionarios van en una sola dirección: dada la clave, podemos buscar el valor, pero dado un valor, no podemos encontrar fácilmente la clave correspondiente. Además, el nombre “diccionario” es un poco engañoso, ya que aunque los diccionarios reales están ordenados en orden alfabético, los diccionarios Python no tienen un orden intrínseco. A diferencia de las listas, que están ordenadas y tienen un primer y último elemento, Python no garantiza cómo se almacenan internamente los pares clave/valor. Además, cada clave única solo puede estar presente una vez en el diccionario y asociada a un valor, aunque ese valor podría ser algo complejo como una lista, o incluso otro diccionario. Quizás una mejor analogía sería un conjunto de estantes etiquetados, donde cada etiqueta solo se puede usar una vez.

Hay una variedad de funciones y métodos que podemos usar para operar en diccionarios en Python. Por ejemplo, la función len ()
devolverá el número de pares clave/valor en un diccionario. Para el diccionario anterior, len (ids_to_gcs)
devolverá 3
. Si queremos, podemos obtener una lista de todas las claves de un diccionario usando su método.keys ()
, aunque esta lista puede estar en un orden aleatorio porque los diccionarios están desordenados. Siempre podríamos ordenar la lista, y pasar por encima de eso:

Del mismo modo, podemos obtener una lista de todos los valores usando .values ()
, nuevamente sin ningún orden en particular. Entonces, ids_to_gcs.values ()
devolverá una lista de tres flotadores. [2]
Si tratamos de obtener un valor para una clave que no está presente en el diccionario, obtendremos un KeyError
. Entonces, por lo general vamos a querer probar si una clave está presente antes de intentar leer su valor. Podemos hacer esto con el método .has_key ()
del diccionario, que devuelve True
si la clave está presente en el diccionario. [3]

Contando Términos de Ontología Génica
Para ilustrar el uso de un diccionario en la práctica, considere el archivo Pz.ANNOT.txt
, el resultado de anotar un conjunto de transcripciones ensambladas con términos y números de ontología génica (GO). Cada línea separada por tabuladores da una ID de gen, una anotación con un número GO y un término legible por humanos correspondiente asociado con ese número.

En este archivo, cada gen puede estar asociado con múltiples números GO, y cada número GO puede estar asociado con múltiples genes. Además, cada término GO puede estar asociado con múltiples números GO diferentes. ¿Cuántas veces se encuentra cada ID en este archivo? Idealmente, nos gustaría producir una salida separada por tabuladores que se vea así:

Nuestra estrategia: Para practicar algunos de los conceptos de interacción de línea de comandos, haremos que este programa lea el archivo en la entrada estándar y escriba su salida en la salida estándar (como se discutió en el capítulo 19, “Interfaz de línea de comandos”). Tendremos que mantener un diccionario, donde las claves son los ID de genes y los valores son los recuentos. Un for-loop servirá para leer en cada línea, quitando la nueva línea final y dividiendo el resultado en una lista en el carácter de tabulación,\ t
. Si el ID está en el diccionario, agregaremos uno al valor. Debido a que el diccionario comenzará vacío, frecuentemente nos encontraremos con IDs que no están ya presentes en el diccionario; en estos casos podemos establecer el valor en 1
. Una vez que hayamos procesado toda la entrada, podemos recorrer el diccionario imprimiendo cada conteo e ID.
En el siguiente código (go_id_count.py
), cuando el diccionario tiene la clave seqid
, ambos estamos leyendo del diccionario (en el lado derecho) y escribiendo al valor (en el lado izquierdo).

Pero cuando la clave no está presente, simplemente estamos escribiendo al valor. En el bucle que imprime el contenido del diccionario, no estamos comprobando la presencia de cada id
antes de leerlo para imprimirlo, porque se garantiza que la lista de ids_list
contendrá exactamente esas claves que están en el diccionario, ya que es el resultado de ids_to_counts.keys ()
.

¿Cuál es la ventaja de organizar nuestro programa Python para leer filas y columnas en la entrada estándar y escribir filas y columnas en la salida estándar? Bueno, si conocemos suficientemente bien las herramientas de línea de comandos incorporadas, podemos utilizarlas junto con nuestro programa para otros análisis. Por ejemplo, primero podemos filtrar los datos con grep
para seleccionar aquellas líneas que coincidan con el término transcriptasa
:

El resultado son solo líneas que contienen la palabra “transcriptasa”:

Si entonces alimentamos esos resultados a través de nuestro programa (cat pz.annot.txt | grep 'transcriptase' |.
/go_id_count.py), solo vemos recuentos para IDs entre esas líneas.

Finalmente, podríamos canalizar los resultados a través de wc
para contar estas líneas y determinar cuántos ID se anotaron al menos una vez con ese término (21). Si quisiéramos ver qué ocho genes tenían más anotaciones que coincidieran con la “transcriptasa”, también podríamos hacer eso clasificando los recuentos y usando la cabeza
para imprimir las ocho líneas superiores (aquí estamos rompiendo el comando largo con barras inversas, lo que nos permite seguir escribiendo en la siguiente línea en la terminal). [4]

Parece que el gen PZ32722_B
ha sido anotado como transcriptasa siete veces. Este ejemplo ilustra que, a medida que trabajamos y construimos herramientas, si consideramos cómo podrían interactuar con otras herramientas (incluso otras piezas de código, como funciones), podemos aumentar nuestra eficiencia notablemente.
Extracción de todas las líneas que coinciden con un conjunto de ID
Otra propiedad útil de los diccionarios es que el método.has_key ()
es muy eficiente. Supongamos que teníamos una lista desordenada de cadenas, y queríamos determinar si una cadena en particular se produjo en la lista. Esto se puede hacer, pero requeriría mirar cada elemento (en un for-loop, quizás) para ver si igualaba al que estamos buscando. Si en cambio almacenamos las cadenas como claves en un diccionario (almacenando “present”
, o el número 1
, o cualquier otra cosa en el valor), podríamos usar el método .has_key ()
, que toma un solo paso de tiempo (efectivamente, en promedio) sin importar cuántas claves haya en el diccionario. [5]
Volviendo a la lista GO/ID del último ejemplo, supongamos que tuvimos el siguiente problema: deseamos primero identificar todos aquellos genes (filas en la tabla) que fueron etiquetados con GO:0001539
(que podemos hacer fácilmente con grep
en la línea de comandos), y luego deseamos extraer todas las filas de la tabla que coincide con esos ID para tener una idea de qué otras anotaciones podrían tener esos genes.
En esencia, queremos imprimir todas las entradas de un archivo:

Donde la primera columna coincide con cualquier ID en la primera columna de otra entrada:

Resulta que el problema anterior es común en el análisis de datos (subestableciendo líneas sobre la base de un conjunto de “consulta” de entrada), por lo que tendremos cuidado de diseñar un programa que no sea específico de este conjunto de datos, excepto que los ID en cuestión se encuentran en la primera columna. [6]
Escribiremos un programa llamado match_1st_cols.py
que toma dos entradas: en la entrada estándar, leerá una serie de líneas que tienen los ID de consulta que deseamos extraer, y también tomará un parámetro que especifica el archivo desde el que se deben imprimir las líneas coincidentes. Para esta instancia, nos gustaría poder ejecutar nuestro programa de la siguiente manera:

En términos de código, el programa puede leer primero la entrada de entrada estándar y crear un diccionario que tenga claves correspondientes a cada ID que deseamos extraer (los valores pueden ser cualquier cosa). A continuación, el programa recorrerá las líneas del archivo de entrada (especificado en sys.argv [1]
), y por cada ID lo verificará con .has_key ()
contra el diccionario creado previamente; si se encuentra, se imprime la línea.

Hacer ejecutable el programa (match_1st_cols.py
) y ejecutarlo revela todas las anotaciones para esos ID que están anotados con GO:0001539
.

Como antes, podemos usar esta estrategia para extraer fácilmente todas las líneas que coincidan con una variedad de criterios, simplemente modificando una o ambas entradas. Dada cualquier lista de identificaciones de genes de interés de un colaborador, por ejemplo, podríamos usar eso en la entrada estándar y extraer las líneas correspondientes del archivo GO.
Ejercicios
- Los diccionarios se utilizan a menudo para búsquedas simples. Por ejemplo, un diccionario podría tener claves para las tres secuencias de ADN de pares de bases (
“TGG”
, “GCC
”, “TAG
”, etc.) cuyos valores corresponden a códigos de aminoácidos (correspondientemente,“W”
, “A
”, “*”
para “detener”, etc.). La tabla completa se puede encontrar en la web buscando “tabla de codones de aminoácidos”.Escribe una función llamada
codon_to_aa ()
que tome una sola cadena de tres pares de bases y devuelva una cadena de un carácter con el código de aminoácido correspondiente. Es posible que tengas que definir las 64 posibilidades, ¡así que ten cuidado de no cometer errores tipográficos! Si la entrada no es una cadena de ADN válida de tres pares de bases, la función debería devolver“X”
para significar “desconocido”. Pruebe su función con algunas llamadas comoprint (codon_to_aa (“TGG”))
,print (codon_to_aa (“TAA”))
eprint (codon_to_aa (“BOB”))
. - Combine el resultado de la función
codon_to_aa ()
anterior con la funciónget_windows ()
de los ejercicios del capítulo 18, “Funciones de Python”, para producir una funcióndna_to_aa ()
. Dada una cadena como“AAACTGTCTCTA”
, la función debe devolver su traducción como“KLSL”
. - Usa la función
get_windows ()
para escribir una funcióncount_kmers ()
; debería tomar dos parámetros (una secuencia de ADN y un entero) y devolver un diccionario de k-mers para contar para esos k-mers. Por ejemplo,count_kmers (“AAACTGTCTCTA”, 3)
debería devolver un diccionario con las claves“AAA”
,“AAC”
,“ACT”
,“CTG”
,“TGT”
,“GTC”
,“TCT”
,“CTC”
,“CTA”
y los valores correspondientes1
,1
,1
,1
,1
,1
,2
,1
,1
. (El conteo de K-mer es un paso importante en muchos algoritmos bioinformáticos, incluido el ensamblaje del genoma). - Crear una función
union_dictionaries ()
que toma dos diccionarios como parámetros devuelve su “unión” como diccionario; cuando se encuentra una clave en ambos, el valor mayor debe usarse en la salida. Si diccionariodict_a
mapea“A
"
,“B
"
,“C
"
a3
,2
,6
ydict_b
mapea“B
"
,“C
"
,“D
”"
a7
,4
,1
, por ejemplo, la salida debe mapear“A
"
,“B
"
,“C
"
,“D
"
a3
,7
,6
,1
.
- Las claves de diccionario pueden ser de cualquier tipo inmutable, que incluye enteros y cadenas, pero también incluyen otros tipos más exóticos, como tuplas (listas inmutables). Si bien los flotadores también son inmutables, debido a que las claves se buscan en base a la igualdad y los errores de redondeo pueden agrandarse, generalmente no se recomienda utilizarlas como claves de diccionario.
- En Python 3.0 y posteriores, lo que devuelven
.keys ()
y.values ()
no es técnicamente una lista sino una “vista”, que opera de manera similar a una lista sin usar ninguna memoria adicional. El código que se muestra sigue funcionando, peroids.sort ()
(la versión sort-in-place) no lo haría, ya que las vistas no tienen un método.sort ()
. - También hay un método más específico de Python para consultar una clave en un diccionario:
“TL34_X” en ids_to_gcs
es equivalente aids_to_gcs.has_keys (“TL34_X”)
. Curiosamente, la palabra clavein
también funciona para listas: siids = ["CYP6B”, “CATB”, “AGP4"]
, entonces“TL34_X” en ids
devolveráFalse
. Sin embargo, en este caso cada entrada enids
debe compararse con“TL34_X”
para hacer esta determinación, y asíin
es mucho más lento para las listas que para los diccionarios. El uso de la palabra clavein
para diccionarios es generalmente preferido por la comunidad Python, y de hecho.has_key ()
se ha eliminado en Python 3.0 y posteriores. Para los fines de este libro, usaremos.has_key ()
al trabajar con diccionarios para que quede claro exactamente lo que está sucediendo. - Para problemas simples como este, si conocemos suficientemente bien las herramientas de línea de comandos, ni siquiera necesitamos usar Python. Esta versión del problema se puede resolver con una tubería como
cat Pz.Anot.txt | grep 'transcriptasa' | awk '{print $1}' | sort | uniq -c | sort -k1,1nr | head
. -
En términos de informática, decimos que la búsqueda de una lista desordenada se ejecuta en el tiempo
, o “orden”
, donde
se toma para que sea el tamaño de la lista. Se ejecuta el
método.has_key ()
, es decir, que el tiempo empleado es independiente del tamaño del diccionario. Si necesitamos hacer esa búsqueda muchas veces, estas diferencias pueden sumar significativamente. Más información sobre consideraciones de tiempo de ejecución se trata en el capítulo 25, “Algoritmos y estructuras de datos”.
- La utilidad
grep
puede realizar una operación similar;grep -f query_patterns.txt subject_file.txt
imprimirá todas las líneas ensubject_file.txt
que coincidan con cualquiera de los patrones enquery_patterns.txt
. Pero esto requiere que todos los patrones se comparen con todas las líneas (incluso si los patrones son simples), y así nuestra solución personalizada de Python es mucho más rápida cuando el número de consultas es grande (porque el método.has_key ()
es muy rápido).