Estoy intentando cargar muchos CSV de diferentes clientes que contienen los mismos tipos de datos, pero con diferentes nombres de columna. Por ejemplo

Source | Medium | Date
Src | Med | Conversion Date
Came From | Format | DateTime

Todas estas columnas deben considerarse iguales. Así que Source, Src y Came From deben ir a una columna de base de datos "Source". Pueden tener cualquier nombre para diferentes CSV y estar en cualquier orden, por lo que es necesario que se produzca un mapeo cada vez que se crea un cliente diferente.

Pandas tiene una función to_sql, pero esto requiere que ingrese manualmente los nombres de las columnas y no quiero un montón de tablas diferentes, porque necesito mostrar la misma tabla para cada cliente más adelante.

Una solución que podría implementar es hacer que la interfaz requiera que el administrador seleccione manualmente las columnas y las haga coincidir con el nombre de la columna "maestra" correspondiente. Luego, en el backend, simplemente cambie el nombre de esas columnas antes de ejecutar to_sql.

¿Hay alguna otra forma que sea más eficiente para realizar esto? ¿Quizás iterar a través del marco de datos y manejar las cosas fila por fila?

1
Sean Payne 1 sep. 2020 a las 06:43

1 respuesta

La mejor respuesta

Creo que la mejor manera es crear una tabla para las relaciones (alias -> columna de destino) o config file. Este es solo un ejemplo, pero creo que puede comprender mi enfoque:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, String, Integer

engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base(bind=engine)


class ClientAlias(Base):
    # table for dynamic aliases
    __tablename__ = 'client_alias'
    id = Column(Integer, primary_key=True)
    alias = Column(String)
    target = Column(String)


class FinalTable(Base):
    # result table with standardized columns  - for all clients
    __tablename__ = 'final_table'
    id = Column(Integer, primary_key=True)
    client_id = Column(Integer)
    source = Column(String)
    medium = Column(String)


Base.metadata.create_all(engine)


def prepare_aliases():
    """
    insert default mapping:
    Src -> source, Came From -> source, Med -> medium, etc...
    """
    for target, aliases in (
        ('source', ('Source', 'Src', 'Came From'), ),
        ('medium', ('Medium', 'Med', 'Format'), ),
    ):
        for alias in aliases:
            session.add(ClientAlias(target=target, alias=alias))

    session.commit()

# insert a few records with client column aliases
prepare_aliases()

# example processing
dfs = (
    # first client with specific columns
    pd.DataFrame.from_dict({
        'client_id': (1, 1, ),
        'Source': ('Source11', 'Source12'),
        'Medium': ('Medium11', 'Medium12'),
    }),
    # second client with specific columns
    pd.DataFrame.from_dict({
        'client_id': (2, 2, ),
        'Src': ('Source12', 'Source22'),
        'Med': ('Medium12', 'Medium22'),
    }),
    # one more client with specific columns
    pd.DataFrame.from_dict({
        'client_id': (3, 3, ),
        'Came From': ('Source13', 'Source23'),
        'Format': ('Medium13', 'Medium23'),
    }),
    # etc...
)

# create columns map {Src -> source, Came From -> source, ect...}
columns = {c.alias: c.target for c in session.query(ClientAlias).all()}
for df in dfs:
    df.rename(columns=columns, inplace=True)

# union and insert into final table
df = pd.concat(dfs, sort=False, ignore_index=True)
df.to_sql(
    con=engine,
    name=FinalTable.__tablename__,
    index=False,
    if_exists='append'
)

Por lo tanto, puede agregar un nuevo registro en client_alias (o en config file) si tendrá un nuevo cliente o habrá algunos cambios. Y todo funcionará bien sin cambios de código y sin implementación. De todos modos, esto es solo un ejemplo: puede personalizarlo como desee.

1
Danila Ganchar 3 sep. 2020 a las 20:27