tablas - funciones panda python
¿Cuál es la mejor estructura de datos para almacenar un conjunto de cuatro(o más) valores? (4)
¿Existe una estructura de datos en Python que permita almacenar un registro con
n
columnas (nombre, edad, sexo, peso, altura, etc.) y recuperar registros basados en cualquier (uno) de la columna en logarítmico (o idealmente constante - O (1) tiempo de búsqueda) complejidad?
No, no hay ninguno. Pero podría intentar implementar uno sobre la base de un diccionario por dimensión de valor. Siempre y cuando tus valores sean manejables por supuesto. Si implementa una clase personalizada para sus registros, cada diccionario contendrá referencias a los mismos objetos. Esto te ahorrará algo de memoria.
Supongamos que tengo las siguientes variables
y sus values
correspondientes values
que representan un record
.
name = ''abc''
age = 23
weight = 60
height = 174
Tenga en cuenta que el value
podría ser de diferentes types
( string
, integer
, float
, referencia a cualquier otro objeto, etc.).
Habrá muchos records
(al menos> 100.000). Todos y cada uno de los record
serán unique
cuando se unique
todas estas cuatro variables
(en realidad sus values
). En otras palabras, no existe ningún record
con los 4 values
son iguales.
Estoy tratando de encontrar una estructura de datos eficiente en Python
que me permita (almacenar y) recuperar records
basados en cualquiera de estas variables
en la complejidad del tiempo de log(n)
.
Por ejemplo:
def retrieve(name=None,age=None,weight=None,height=None)
if name is not None and age is None and weight is None and height is None:
/* get all records with the given name */
if name is None and age is not None and weight is None and height is None:
/* get all records with the given age */
....
return records
La forma en que debe llamarse la retrieve
es la siguiente:
retrieve(name=''abc'')
Lo anterior debe devolver [{name:''abc'', age:23, wight:50, height=175}, {name:''abc'', age:28, wight:55, height=170}, etc]
retrieve(age=23)
Lo anterior debe devolver [{name:''abc'', age:23, wight:50, height=175}, {name:''def'', age:23, wight:65, height=180}, etc]
Y, puede que necesite agregar una o dos variables
más a este registro en el futuro. Por ejemplo, digamos, sex = ''m''
. Entonces, la función de retrieve
debe ser escalable.
En resumen: ¿hay una estructura de datos en Python
que permita storing a record
con n
columns
(nombre, edad, sexo, peso, altura, etc.) y retrieving records
basados en cualquier (uno) de la column
en logarithmic
( o idealmente constant - O(1)
búsqueda) complejidad?
Dado http://wiki.python.org/moin/TimeComplexity, ¿qué tal esto?
- Tenga un diccionario para cada columna que le interese:
AGE
,NAME
, etc. - Tener las claves de esos diccionarios (
AGE
,NAME
) como valores posibles para una columna dada (35 o "m"). - Tener una lista de listas que representan los valores de una "colección", por ejemplo,
VALUES = [ [35, "m"], ...]
- Haga que el valor de los diccionarios de columnas (
AGE
,NAME
) sean listas de índices de la listaVALUES
. - Tenga un diccionario que mapee el nombre de la columna para indexar dentro de las listas en
VALUES
para que sepa que la primera columna es la edad y la segunda el sexo (puede evitar eso y usar diccionarios, pero introducen la memoria grande footrpint y con más de 100 K objetos esto puede o no ser un problema).
Entonces la función de retrieve
podría verse así:
def retrieve(column_name, column_value):
if column_name == "age":
return [VALUES[index] for index in AGE[column_value]]
elif ...: # repeat for other "columns"
Entonces, esto es lo que obtienes
VALUES = [[35, "m"], [20, "f"]]
AGE = {35:[0], 20:[1]}
SEX = {"m":[0], "f":[1]}
KEYS = ["age", "sex"]
retrieve("age", 35)
# [[35, ''m'']]
Si quieres un diccionario, puedes hacer lo siguiente:
[dict(zip(KEYS, values)) for values in retrieve("age", 35)]
# [{''age'': 35, ''sex'': ''m''}]
pero, una vez más, los diccionarios son un poco pesados por el lado de la memoria, por lo que si puede ir con listas de valores, podría ser mejor.
Tanto la recuperación de diccionario como de la lista son O (1) en promedio; el peor caso para el diccionario es O (n), por lo que debería ser bastante rápido. Mantener eso será un poco doloroso, pero no tanto. Para "escribir", solo tiene que agregar a la lista VALUES
y luego anexar el índice en VALUES
a cada uno de los diccionarios.
Por supuesto, lo mejor sería comparar su implementación real y buscar mejoras potenciales, pero espero que esto tenga sentido y lo haga funcionar :)
EDITAR:
Tenga en cuenta que, como dijo @moooeeeep, esto solo funcionará si sus valores son manejables y, por lo tanto, pueden utilizarse como claves del diccionario.
Puede lograr la complejidad de tiempo logarítmico en una base de datos relacional usando índices ( O(log(n)**k)
con índices de columna individual). Luego, para recuperar datos solo construye SQL apropiado:
names = {''name'', ''age'', ''weight'', ''height''}
def retrieve(c, **params):
if not (params and names.issuperset(params)):
raise ValueError(params)
where = '' and ''.join(map(''{0}=:{0}''.format, params))
return c.execute(''select * from records where '' + where, params)
Ejemplo:
import sqlite3
c = sqlite3.connect('':memory:'')
c.row_factory = sqlite3.Row # to provide key access
# create table
c.execute("""create table records
(name text, age integer, weight real, height real)""")
# insert data
records = ((''abc'', 23, 60, 174+i) for i in range(2))
c.executemany(''insert into records VALUES (?,?,?,?)'', records)
# create indexes
for name in names:
c.execute("create index idx_{0} on records ({0})".format(name))
try:
retrieve(c, naame=''abc'')
except ValueError:
pass
else:
assert 0
for record in retrieve(c, name=''abc'', weight=60):
print(record[''height''])
Salida:
174.0
175.0
No hay una sola estructura de datos integrada en Python que haga todo lo que quiera, pero es bastante fácil usar una combinación de las que tiene para lograr sus objetivos y hacerlo de manera bastante eficiente.
Por ejemplo, supongamos que su entrada fue la siguiente información en un archivo de valores separados por comas llamado employees.csv
con nombres de campo definidos como se muestra en la primera línea:
name,age,weight,height
Bob Barker,25,175,6ft 2in
Ted Kingston,28,163,5ft 10in
Mary Manson,27,140,5ft 6in
Sue Sommers,27,132,5ft 8in
Alice Toklas,24,124,5ft 6in
El siguiente es un código de trabajo que ilustra cómo leer y almacenar estos datos en una lista de registros, y automáticamente crea tablas de búsqueda separadas para encontrar registros asociados con los valores de los campos contenidos en cada uno de estos registros.
Los registros son instancias de una clase creada por namedtuple
que es muy eficiente en la memoria porque carece de un atributo __dict__
que las instancias de clase normalmente contienen. Al usarlos, será posible acceder a los campos de cada uno por nombre usando la sintaxis de punto, como record.fieldname
.
Las tablas de búsqueda son instancias de defaultdict(list)
, que proporcionan tiempos de búsqueda O (1) similares a los diccionarios en promedio, y también permiten que se asocien múltiples valores con cada uno. Entonces, la clave de búsqueda es el valor del valor de campo buscado y los datos asociados con él serán una lista de los índices enteros de los registros de Person
almacenados en la lista de employees
con ese valor, por lo que todos serán relativamente pequeños. .
Tenga en cuenta que el código para la clase está completamente basado en datos, ya que no contiene ningún nombre de campo codificado que, en cambio, se toma de la primera fila del archivo de entrada de datos csv cuando se lee. Cuando se utiliza una instancia, cualquier retrieve()
llamadas al método de retrieve()
deben, por supuesto, contener argumentos válidos de nombre de campo válido.
Actualizar
Modificado para no crear una tabla de búsqueda para cada valor único de cada campo cuando el archivo de datos se lee por primera vez. Ahora el método retrieve()
crea solo según sea necesario (y guarda / almacena el resultado en caché para usarlo en el futuro).
from collections import defaultdict, namedtuple
import csv
class DataBase(object):
def __init__(self, csv_filename, recordname):
# read data from csv format file into a list of namedtuples
with open(csv_filename, ''rb'') as inputfile:
csv_reader = csv.reader(inputfile, delimiter='','')
self.fields = csv_reader.next() # read header row
self.Record = namedtuple(recordname, self.fields)
self.records = [self.Record(*row) for row in csv_reader]
self.valid_fieldnames = set(self.fields)
# create an empty table of lookup tables for each field name that maps
# each unique field value to a list of record-list indices of the ones
# that contain it
self.lookup_tables = defaultdict(lambda: defaultdict(list))
def retrieve(self, **kwargs):
""" Fetch a list of records with a field name with the value supplied
as a keyword arg (or return None if there aren''t any). """
if len(kwargs) != 1: raise ValueError(
''Exactly one fieldname/keyword argument required for function ''
''(%s specified)'' % '', ''.join([repr(k) for k in kwargs.keys()]))
field, value = kwargs.items()[0] # get only keyword arg and value
if field not in self.valid_fieldnames:
raise ValueError(''keyword arg "%s" isn/'t a valid field name'' % field)
if field not in self.lookup_tables: # must create field look up table
for index, record in enumerate(self.records):
value = getattr(record, field)
self.lookup_tables[field][value].append(index)
matches = [self.records[index]
for index in self.lookup_tables[field].get(value, [])]
return matches if matches else None
if __name__ == ''__main__'':
empdb = DataBase(''employees.csv'', ''Person'')
print "retrieve(name=''Ted Kingston''):", empdb.retrieve(name=''Ted Kingston'')
print "retrieve(age=''27''):", empdb.retrieve(age=''27'')
print "retrieve(weight=''150''):", empdb.retrieve(weight=''150'')
try:
print "retrieve(hight=''5ft 6in''):", empdb.retrieve(hight=''5ft 6in'')
except ValueError as e:
print "ValueError(''{}'') raised as expected".format(e)
else:
raise type(''NoExceptionError'', (Exception,), {})(
''No exception raised from "retrieve(hight=/'5ft/')" call.'')
Salida:
retrieve(name=''Ted Kingston''): [Person(name=''Ted Kingston'', age=''28'', weight=''163'', height=''5ft 10in'')]
retrieve(age=''27''): [Person(name=''Mary Manson'', age=''27'', weight=''140'', height=''5ft 6in''),
Person(name=''Sue Sommers'', age=''27'', weight=''132'', height=''5ft 8in'')]
retrieve(weight=''150''): None
retrieve(hight=''5ft 6in''): ValueError(''keyword arg "hight" is an invalid fieldname'')
raised as expected