从pandas转换为numpy时如何保留列名

时间:2016-11-11 18:37:44

标签: python pandas numpy

根据to this post,我应该能够访问ndarray中列的名称作为a.dtype.names

但是,如果我将pandas DataFrame转换为带有df.as_matrix()或df.values的ndarray,则dtype.names字段为None。另外,如果我尝试将列名分配给ndarray

ValueError: there are no fields defined

我得到了

$form = $this->createForm(TaskType::class, $task, array(
    'action' => $this->generateUrl('target_route'),
    'method' => 'GET',
));

更新:

我特别感兴趣的是矩阵只需要保持一种类型(它是特定数字类型的ndarray),因为我也喜欢使用cython进行优化。 (我怀疑numpy记录和结构化数组更难处理,因为它们更自由地输入。)

真的,我只想维护通过sci-kit预测器深层树的数组的column_name元数据。它的界面的.fit(X,y)和.predict(X)API不允许传递关于X和y对象之外的列标签的附加元数据。

5 个答案:

答案 0 :(得分:6)

考虑DF,如下所示:

X = pd.DataFrame(dict(one=['Strawberry', 'Fields', 'Forever'], two=[1,2,3]))
X

enter image description here

提供元组列表作为结构化数组的数据输入:

arr_ip = [tuple(i) for i in X.as_matrix()]

有序的字段名称列表:

dtyp = np.dtype(list(zip(X.dtypes.index, X.dtypes)))

此处,X.dtypes.index为您提供了列名和X.dtypes对应的dtypes,这些dtypes再次统一到元组列表中并作为输入提供给dtype要构造的元素。

arr = np.array(arr_ip, dtype=dtyp)

给出:

arr
# array([('Strawberry', 1), ('Fields', 2), ('Forever', 3)], 
#       dtype=[('one', 'O'), ('two', '<i8')])

arr.dtype.names
# ('one', 'two')

答案 1 :(得分:2)

Pandas数据框也有一个方便的to_records方法。演示:

X = pd.DataFrame(dict(age=[40., 50., 60.], 
                      sys_blood_pressure=[140.,150.,160.]))
m = X.to_records(index=False)
print repr(m)

返回:

rec.array([(40.0, 140.0), (50.0, 150.0), (60.0, 160.0)], 
          dtype=[('age', '<f8'), ('sys_blood_pressure', '<f8')])

这是一个"record array",它是一个ndarray子类,允许使用属性进行字段访问,例​​如m.age以外的m['age']

您可以通过构建视图将其作为常规float数组传递给cython函数:

m_float = m.view(float).reshape(m.shape + (-1,))
print repr(m_float)

给出了:

rec.array([[  40.,  140.],
           [  50.,  150.],
           [  60.,  160.]], 
          dtype=float64)

请注意,为了使其正常工作,原始Dataframe必须为每列都有一个float dtype。确保使用m = X.astype(float, copy=False).to_records(index=False)

答案 2 :(得分:1)

创建一个例子:

import pandas
import numpy
PandasTable = pandas.DataFrame( {
"AAA": [4, 5, 6, 7], 
"BBB": [10, 20, 30, 40], 
"CCC": [100, 50, -30, -50], 
"DDD": ['asdf1', 'asdf2', 'asdf3', 'asdf4'] } )

解决这个问题,注意我们正在创建一个叫做“结构化 numpy 数组”的东西:

NumpyDtypes             = list( PandasTable.dtypes.items() )
NumpyTable              = PandasTable.to_numpy(copy=True)
NumpyTableRows          = [ tuple(Row) for Row in NumpyTable]
NumpyTableWithHeaders   = numpy.array( NumpyTableRows, dtype=NumpyDtypes )

用 1 行代码重写解决方案:

NumpyTableWithHeaders2   = numpy.array( [ tuple(Row) for Row in PandasTable.to_numpy(copy=True)], dtype=list( PandasTable.dtypes.items() ) )

打印出解决方案的结果:

print ('NumpyTableWithHeaders', NumpyTableWithHeaders)
print ('NumpyTableWithHeaders.dtype', NumpyTableWithHeaders.dtype)
print ('NumpyTableWithHeaders2', NumpyTableWithHeaders2)
print ('NumpyTableWithHeaders2.dtype', NumpyTableWithHeaders2.dtype)
<块引用>
NumpyTableWithHeaders [(4, 10, 100, 'asdf1') (5, 20,  50, 'asdf2') (6, 30, -30, 'asdf3')
 (7, 40, -50, 'asdf4')]
NumpyTableWithHeaders.dtype [('AAA', '<i8'), ('BBB', '<i8'), ('CCC', '<i8'), ('DDD', 'O')]
NumpyTableWithHeaders2 [(4, 10, 100, 'asdf1') (5, 20,  50, 'asdf2') (6, 30, -30, 'asdf3')
 (7, 40, -50, 'asdf4')]
NumpyTableWithHeaders2.dtype [('AAA', '<i8'), ('BBB', '<i8'), ('CCC', '<i8'), ('DDD', 'O')]

我必须阅读的文档

Adding row/column headers to NumPy arrays

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.to_numpy.html

How to keep column names when converting from pandas to numpy

https://numpy.org/doc/stable/user/basics.creation.html

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.dtypes.html

https://docs.scipy.org/doc/numpy-1.10.1/user/basics.rec.html

注意事项和想法: Pandas 应该在他们的 'to_numpy' 函数中添加一个标志来执行此操作。 应更新最新版本的 Numpy 文档以包含结构化数组,其行为与常规数组不同。

答案 3 :(得分:0)

好的,我在这里倾斜:

class NDArrayWithColumns(np.ndarray):
    def __new__(cls, obj,  columns=None):
        obj = obj.view(cls)
        obj.columns = columns
        return obj

    def __array_finalize__(self, obj):
        if obj is None: return
        self.columns = getattr(obj, 'columns', None)

    @staticmethod
    def from_dataframe(df):
        cols = tuple(df.columns)
        arr = df.as_matrix(cols)
        return NDArrayWithColumns.from_array(arr,cols)

    @staticmethod
    def from_array(array,columns):
        if isinstance(array,NDArrayWithColumns):
            return array
        return NDArrayWithColumns(array,tuple(columns))

    def __str__(self):
        sup = np.ndarray.__str__(self)
        if self.columns:
            header = ", ".join(self.columns)
            header = "# " + header + "\n"
            return header+sup
        return sup

NAN = float("nan")
X = pd.DataFrame(dict(age=[40., NAN, 60.], sys_blood_pressure=[140.,150.,160.]))
arr = NDArrayWithColumns.from_dataframe(X)
print arr
print arr.columns
print arr.dtype

给出:

# age, sys_blood_pressure
[[  40.  140.]
 [  nan  150.]
 [  60.  160.]]
('age', 'sys_blood_pressure')
float64

并且也可以传递给类型为期望ndarray [2,double_t]的cython函数。

更新:除了oddness when passing the type to ufuncs之外,这项工作非常好。

答案 4 :(得分:0)

还有更多将pandas.DataFrame转换为numpy.array的方法,同时保留标签/列名

  

这主要是为了演示如何设置dtype / column_dtypes,因为有时数据源迭代器的输出需要进行一些预规范化。


一种方法将列逐行插入到预定义的 height 的归零数组中,并且该方法宽松地基于Creating Structured Arrays指南,只是出现了一些网络爬虫

import numpy


def to_tensor(dataframe, columns = [], dtypes = {}):
    # Use all columns from data frame if none where listed when called
    if len(columns) <= 0:
        columns = dataframe.columns
    # Build list of dtypes to use, updating from any `dtypes` passed when called
    dtype_list = []
    for column in columns:
        if column not in dtypes.keys():
            dtype_list.append(dataframe[column].dtype)
        else:
            dtype_list.append(dtypes[column])
    # Build dictionary with lists of column names and formatting in the same order
    dtype_dict = {
        'names': columns,
        'formats': dtype_list
    }
    # Initialize _mostly_ empty nupy array with column names and formatting
    numpy_buffer = numpy.zeros(
        shape = len(dataframe),
        dtype = dtype_dict)
    # Insert values from dataframe columns into numpy labels
    for column in columns:
        numpy_buffer[column] = dataframe[column].to_numpy()
    # Return results of conversion
    return numpy_buffer

方法2基于user7138814answer,并且可能会更加有效,因为它基本上是to_records可用的内置pandas.DataFrame方法的包装

def to_tensor(dataframe, columns = [], dtypes = {}, index = False):
    to_records_kwargs = {'index': index}
    if not columns:  # Default to all `dataframe.columns`
        columns = dataframe.columns
    if dtypes:       # Pull in modifications only for dtypes listed in `columns`
        to_records_kwargs['column_dtypes'] = {}
        for column in dtypes.keys():
            if column in columns:
                to_records_kwargs['column_dtypes'].update({column: dtypes.get(column)})
    return dataframe[columns].to_records(**to_records_kwargs)

使用以上任何一种方法都可以...

X = pandas.DataFrame(dict(age = [40., 50., 60.], sys_blood_pressure = [140., 150., 160.]))

# Example of overwriting dtype for a column
X_tensor = to_tensor(X, dtypes = {'age': 'int32'})

print("Ages -> {0}".format(X_tensor['age']))
print("SBPs -> {0}".format(X_tensor['sys_blood_pressure']))

...应该 输出...

Ages -> array([40, 50, 60])
SBPs -> array([140., 150., 160.])

...以及X_tensor的完整转储应如下所示。

array([(40, 140.), (50, 150.), (60, 160.)],
      dtype=[('age', '<i4'), ('sys_blood_pressure', '<f8')])

一些想法

虽然方法二可能比第一种更有效,但方法一(进行了一些修改)可能对于将两个或更多pandas.DataFrame合并为一个numpy.array

更为有用。