使用SQLAlchemy查询到Pandas DataFrame时重命名列

时间:2015-06-30 20:03:37

标签: python pandas sqlalchemy

在将数据查询到pandas数据帧时,有没有办法保留SqlAlchemy属性名?

这是我的数据库的简单映射。对于学校的桌子,我已将“DBD”的“学校区域”重命名为“更短的区域”。我从DBA中删除了几个层,因此在源代码中更改它们是不可行的。

class School(Base):
    __tablename__ = 'DimSchool'

    id = Column('SchoolKey', Integer, primary_key=True)
    name = Column('SchoolName', String)
    district = Column('SchoolDistrict', String)


class StudentScore(Base):
    __tablename__ = 'FactStudentScore'

    SchoolKey = Column('SchoolKey', Integer, ForeignKey('DimSchool.SchoolKey'), primary_key = True)
    PointsPossible = Column('PointsPossible', Integer)
    PointsReceived = Column('PointsReceived', Integer)

    school = relationship("School", backref='studentscore')

所以当我查询类似的东西时:

query = session.query(StudentScore, School).join(School)
df = pd.read_sql(query.statement, query.session.bind)

我最终得到了基础的学校区'返回的DataFrame df。

中列的名称,而不是我的属性名称

修改: 更令人讨厌的情况是表格中存在重复的列名称。例如:

class Teacher(Base):
    __tablename__ = 'DimTeacher'

    id = Column('TeacherKey', Integer, primary_key=True)
    fname = Column('FirstName', String)
    lname = Column('FirstName', String)

class Student(Base):
    __tablename__ = 'DimStudent'

    id = Column('StudentKey', Integer, primary_key=True)
    fname = Column('FirstName', String)
    lname = Column('FirstName', String)

因此,跨两个表的查询(如下所示)会生成一个具有重复的FirstName和LastName列的数据框。

query = session.query(StudentScore, Student, Teacher).join(Student).join(Teacher)

是否可以在查询时重命名这些列?现在,我无法用这两个列名系统保持头脑清醒。

2 个答案:

答案 0 :(得分:1)

如果我之后必须维护代码,这是一种我会痛苦抱怨的解决方案。但是你的问题有很多限制,我找不到更好的东西。

首先,使用这样的内省构建一个具有模式和类列等价的字典(我使用你发布的第一个例子):

In [132]:

def add_to_dict(c_map, t_map, table):
    name = table.__tablename__
    t_map[name] = table.__name__
    #print name
    c_map[name] = {}
    for column in dir(table):
        c_schema_name = table.__mapper__.columns.get(column)
        if isinstance(c_schema_name, Column):
            #print column, c_schema_name.name
            c_map[name][c_schema_name.name] = column

c_map = {}
t_map = {}
add_to_dict(c_map, t_map, School)
add_to_dict(c_map, t_map, StudentScore)
print c_map['DimSchool']['SchoolKey']
print c_map['FactStudentScore']['SchoolKey']
print t_map['DimSchool']
id
SchoolKey
School

[编辑:关于内省构建词典的方式的澄清

  • c_map是列名称对应词典
  • t_map是表名的对应词典
  • 需要为每个表的每个类调用
  • 对于表名,对应很容易,因为它只是表的类的属性
  • 对于类的列名,1st使用dir
  • 迭代类的属性
  • 对于类的每个属性(将是表的列,还有许多其他内容)尝试使用sqlalchemy mapper
  • 获取数据库列名称
  • 只有当属性真的是列
  • 时,映射器才会返回Column对象
  • 因此对于Column个对象,将它们添加到列名字典中。数据库名称使用.name获取,另一个只是属性

在创建数据库中的所有对象后,只运行一次,每个表类调用一次。]

然后你获取你的sql语句并建立一个你将获得的列的翻译列表:

In [134]:

df_columns = []
for column in str(query.statement).split('FROM')[0].split('SELECT')[1].split(','):
    table = column.split('.')[0].replace('"', '').strip()
    c_schema = column.split('.')[1].replace('"', '').strip()
    df_columns += [t_map[table] + '.' + eq[table][c_schema]]
print df_columns
​
['StudentScore.SchoolKey', 'StudentScore.PointsPossible', 'StudentScore.PointsReceived', 'School.id', 'School.name', 'School.district']

最后,您在问题中阅读数据框并更改列的名称:

In [137]:

df.columns = df_columns
In [138]:

df
Out[138]:
StudentScore.SchoolKey  StudentScore.PointsPossible StudentScore.PointsReceived School.id   School.name School.district
0   1   1   None    1   School1 None

(数据只是我创建的一个愚蠢的注册表。)

希望它有所帮助!

答案 1 :(得分:1)

无论如何我都不是SQLAlchemy专家,但是我想出了一个更通用的解决方案(或者至少是一个开始)。

注意事项

  • 将不会在不同模型之间处理具有相同名称的映射列。您应该通过添加后缀来解决此问题,或者可以在下面修改我的答案以将熊猫列创建为<tablename/model name>.<mapper column name>

它涉及四个关键步骤:

  1. 使用标签限定查询语句,这将导致<table name>_<column name>的熊猫中的列名称:
df = pd.read_sql(query.statement, query.session.bind).with_labels()
  1. 将表名与(实际)列名分开
table_name, col = col_name.split('_', 1)
  1. 基于表名(从this question's answers)获取模型
for c in Base._decl_class_registry.values():
            if hasattr(c, '__tablename__') and c.__tablename__ == tname:
                return c
  1. 找到正确的映射名称
for k, v in sa_class.__mapper__.columns.items():
        if v.name == col:
            return k

将所有内容整合在一起,这是我想出的解决方案,主要警告是,如果您(可能)在整个数据库中有重复的映射名称,则会导致数据框中的列名称重复课程。

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class School(Base):
    __tablename__ = 'DimSchool'

    id = Column('SchoolKey', Integer, primary_key=True)
    name = Column('SchoolName', String)
    district = Column('SchoolDistrict', String)


class StudentScore(Base):
    __tablename__ = 'FactStudentScore'

    SchoolKey = Column('SchoolKey', Integer, ForeignKey('DimSchool.SchoolKey'), primary_key = True)
    PointsPossible = Column('PointsPossible', Integer)
    PointsReceived = Column('PointsReceived', Integer)

    school = relationship("School", backref='studentscore')


def mapped_col_name(col_name):
    ''' Retrieves mapped Model based on
    actual table name (as given in pandas.read_sql)
    '''

    def sa_class(table_name):
        for c in Base._decl_class_registry.values():
            if hasattr(c, '__tablename__') and c.__tablename__ == tname:
                return c

    table_name, col = col_name.split('_', 1)
    sa_class = sa_class(table_name)

    for k, v in sa_class.__mapper__.columns.items():
        if v.name == col:
            return k

query = session.query(StudentScore, School).join(School)
df = pd.read_sql(query.statement, query.session.bind).with_labels()
df.columns = map(mapped_col_name, df.columns)