将 Pandas DataFrame 转换为 Orange 表格

14

我注意到这已经是一个 GitHub 上的问题了。有没有人有将 Pandas DataFrame 转换为 Orange Table 的代码?

明确地说,我有以下表格。

       user  hotel  star_rating  user  home_continent  gender
0         1     39          4.0     1               2  female
1         1     44          3.0     1               2  female
2         2     63          4.5     2               3  female
3         2      2          2.0     2               3  female
4         3     26          4.0     3               1    male
5         3     37          5.0     3               1    male
6         3     63          4.5     3               1    male

橙色格式看起来并不难输出:http://docs.orange.biolab.si/reference/rst/Orange.data.formats.html 它还支持导入CSV文件并猜测数据类型,你试过吗? - EdChum
我能理解数据是如何保存到*.tab文件中的,但具体来说,是否有一个函数或一系列调用可以让你将panda DataFrame转换为Orange Table?(旁注:有趣的是,该页面谈论了数据如何存储在外部文件中,但没有谈论如何保存/加载文件。我个人认为Orange文档不够完善。) - hlin117
将 Pandas 表格保存为文件,然后在 Orange 中导入该文件的工作流程可行吗?还是太过于笨拙了?我猜字段数据类型可能无法很好地传递。 - BKay
@BKay 这是一个开始,但我正在寻找更优雅或更直接的解决方案。实际上,这听起来像是 EdChum 的想法。 - hlin117
8个回答

19
Orange包的文档没有涵盖所有细节。根据lib_kernel.cpp,Table._init__(Domain, numpy.ndarray)仅适用于intfloat
他们确实应该为pandas.DataFrames提供一个C级别接口,或者至少支持numpy.dtype("str")更新:通过利用numpy对int和float进行table2dfdf2table的性能得到了极大的改善。
将此脚本保存在您的orange python脚本集合中,现在您的orange环境中配备了pandas。 用法a_pandas_dataframe = table2df( a_orange_table )a_orange_table = df2table( a_pandas_dataframe ) 注意:此脚本仅适用于Python 2.x,请参考@DustinTang的答案以获取Python 3.x兼容脚本。
import pandas as pd
import numpy as np
import Orange

#### For those who are familiar with pandas
#### Correspondence:
####    value <-> Orange.data.Value
####        NaN <-> ["?", "~", "."] # Don't know, Don't care, Other
####    dtype <-> Orange.feature.Descriptor
####        category, int <-> Orange.feature.Discrete # category: > pandas 0.15
####        int, float <-> Orange.feature.Continuous # Continuous = core.FloatVariable
####                                                 # refer to feature/__init__.py
####        str <-> Orange.feature.String
####        object <-> Orange.feature.Python
####    DataFrame.dtypes <-> Orange.data.Domain
####    DataFrame.DataFrame <-> Orange.data.Table = Orange.orange.ExampleTable 
####                              # You will need this if you are reading sources

def series2descriptor(d, discrete=False):
    if d.dtype is np.dtype("float"):
        return Orange.feature.Continuous(str(d.name))
    elif d.dtype is np.dtype("int"):
        return Orange.feature.Continuous(str(d.name), number_of_decimals=0)
    else:
        t = d.unique()
        if discrete or len(t) < len(d) / 2:
            t.sort()
            return Orange.feature.Discrete(str(d.name), values=list(t.astype("str")))
        else:
            return Orange.feature.String(str(d.name))


def df2domain(df):
    featurelist = [series2descriptor(df.icol(col)) for col in xrange(len(df.columns))]
    return Orange.data.Domain(featurelist)


def df2table(df):
    # It seems they are using native python object/lists internally for Orange.data types (?)
    # And I didn't find a constructor suitable for pandas.DataFrame since it may carry
    # multiple dtypes
    #  --> the best approximate is Orange.data.Table.__init__(domain, numpy.ndarray),
    #  --> but the dtype of numpy array can only be "int" and "float"
    #  -->  * refer to src/orange/lib_kernel.cpp 3059:
    #  -->  *    if (((*vi)->varType != TValue::INTVAR) && ((*vi)->varType != TValue::FLOATVAR))
    #  --> Documents never mentioned >_<
    # So we use numpy constructor for those int/float columns, python list constructor for other

    tdomain = df2domain(df)
    ttables = [series2table(df.icol(i), tdomain[i]) for i in xrange(len(df.columns))]
    return Orange.data.Table(ttables)

    # For performance concerns, here are my results
    # dtndarray = np.random.rand(100000, 100)
    # dtlist = list(dtndarray)
    # tdomain = Orange.data.Domain([Orange.feature.Continuous("var" + str(i)) for i in xrange(100)])
    # tinsts = [Orange.data.Instance(tdomain, list(dtlist[i]) )for i in xrange(len(dtlist))] 
    # t = Orange.data.Table(tdomain, tinsts)
    #
    # timeit list(dtndarray)  # 45.6ms
    # timeit [Orange.data.Instance(tdomain, list(dtlist[i])) for i in xrange(len(dtlist))] # 3.28s
    # timeit Orange.data.Table(tdomain, tinsts) # 280ms

    # timeit Orange.data.Table(tdomain, dtndarray) # 380ms
    #
    # As illustrated above, utilizing constructor with ndarray can greatly improve performance
    # So one may conceive better converter based on these results


def series2table(series, variable):
    if series.dtype is np.dtype("int") or series.dtype is np.dtype("float"):
        # Use numpy
        # Table._init__(Domain, numpy.ndarray)
        return Orange.data.Table(Orange.data.Domain(variable), series.values[:, np.newaxis])
    else:
        # Build instance list
        # Table.__init__(Domain, list_of_instances)
        tdomain = Orange.data.Domain(variable)
        tinsts = [Orange.data.Instance(tdomain, [i]) for i in series]
        return Orange.data.Table(tdomain, tinsts)
        # 5x performance


def column2df(col):
    if type(col.domain[0]) is Orange.feature.Continuous:
        return (col.domain[0].name, pd.Series(col.to_numpy()[0].flatten()))
    else:
        tmp = pd.Series(np.array(list(col)).flatten())  # type(tmp) -> np.array( dtype=list (Orange.data.Value) )
        tmp = tmp.apply(lambda x: str(x[0]))
        return (col.domain[0].name, tmp)

def table2df(tab):
    # Orange.data.Table().to_numpy() cannot handle strings
    # So we must build the array column by column,
    # When it comes to strings, python list is used
    series = [column2df(tab.select(i)) for i in xrange(len(tab.domain))]
    series_name = [i[0] for i in series]  # To keep the order of variables unchanged
    series_data = dict(series)
    print series_data
    return pd.DataFrame(series_data, columns=series_name)

看起来你提供了非常详细的回答,谢谢!这些函数适用于每个Orange表格/Panda数据框吗? - hlin117
希望是的,我已经在我的数据集上测试过了,但可能需要更多的测试。 - TurtleIzzy
这在我的Python3和Orange3中没有起作用。不过还是谢谢! - john doe
1
@DustinTang的回答(见下文)适用于Python 3.5和Orange 3.10。 - pedrovgp
谢谢@pedrovgp的提醒。我已经修改了我的答案。 - TurtleIzzy
这个不再适用,除非你使用的是旧版本的Python、Orange和Pandas。 请参考这个答案:https://stackoverflow.com/a/61260059/1874167 - undefined

11

以下是来自于Github上已关闭的一个问题的答案:

from Orange.data.pandas_compat import table_from_frame
out_data = table_from_frame(df)

df是您的数据帧。到目前为止,我只注意到如果数据源不是100%干净并符合所需的ISO标准,需要手动定义域来处理日期。

我意识到这是一个旧问题,从提问时起发生了很多变化 - 但是这个问题在谷歌搜索结果中排名靠前。


感谢@Creo,已点赞。简单的解决方案,截至2020年8月4日适用于Python 3.7.6。 - GeekLad
我一直在苦苦思索,试图找出解决方法,这正是我正在寻找的解决方案,谢谢@Creo。 - 8TrackRobot

9
from Orange.data.pandas_compat import table_from_frame,table_to_frame
df= table_to_frame(in_data)
#here you go
out_data = table_from_frame(df)

基于Creo的回答


5

这段代码是基于 @TurtleIzzy 的 Python3 版本进行修改的。

import numpy as np
from Orange.data import Table, Domain, ContinuousVariable, DiscreteVariable


def series2descriptor(d):
    if d.dtype is np.dtype("float") or d.dtype is np.dtype("int"):
        return ContinuousVariable(str(d.name))
    else:
        t = d.unique()
        t.sort()
        return DiscreteVariable(str(d.name), list(t.astype("str")))

def df2domain(df):
    featurelist = [series2descriptor(df.iloc[:,col]) for col in range(len(df.columns))]
    return Domain(featurelist)

def df2table(df):
    tdomain = df2domain(df)
    ttables = [series2table(df.iloc[:,i], tdomain[i]) for i in range(len(df.columns))]
    ttables = np.array(ttables).reshape((len(df.columns),-1)).transpose()
    return Table(tdomain , ttables)

def series2table(series, variable):
    if series.dtype is np.dtype("int") or series.dtype is np.dtype("float"):
        series = series.values[:, np.newaxis]
        return Table(series)
    else:
        series = series.astype('category').cat.codes.reshape((-1,1))
        return Table(series)

感谢您提供这段代码。直接调用“Table”已经不再建议,我们必须使用“Table.from_numpy”代替:<string>:27:OrangeDeprecationWarning:“在对Table(X,Y,metas)进行调用时省略域名是不建议的,将被删除。请改用Table.from_numpy(None,X,Y,metas)。”目前编辑队列已满,如果我记得会稍后编辑您的帖子。 - MayeulC

5
为了将pandas DataFrame转换为Orange Table,您需要构建一个域(domain),其中指定列的类型。
对于连续变量,您只需要提供变量的名称,但对于离散变量,您还需要提供所有可能值的列表。
以下代码将为您的DataFrame构建一个域并将其转换为Orange表:
import numpy as np
from Orange.feature import Discrete, Continuous
from Orange.data import Domain, Table
domain = Domain([
    Discrete('user', values=[str(v) for v in np.unique(df.user)]),
    Discrete('hotel', values=[str(v) for v in np.unique(df.hotel)]),
    Continuous('star_rating'),
    Discrete('user', values=[str(v) for v in np.unique(df.user)]),
    Discrete('home_continent', values=[str(v) for v in np.unique(df.home_continent)]),
    Discrete('gender', values=['male', 'female'])], False)
table = Table(domain, [map(str, row) for row in df.as_matrix()])

需要进行map(str, row)步骤,这样Orange才能知道数据包含离散特征的值(而不是值在值列表中的索引)。


这个很好用!我测试了一下,似乎可以按性别对表格进行排序,所以我会假设大多数其他表格功能也能正常工作。 - hlin117
如果您想描述一个特征作为ID,是否没有其他数据类型可用?(例如,用户ID) - hlin117

2

我尝试这个时,出现了一个域错误。"TypeError: invalid arguments for constructor (domain or examples or both expected)"。你能提供一些代码来添加一个域吗? - hlin117
1
假设你有一个 df = DataFrame({"A": [1, 2, 3, 4], "B": [8, 7, 6, 5]})。使用 domain = Orange.data.Domain([Orange.feature.Continuous(name) for name in df.columns]) 构建一个域,然后使用 table = Orange.data.Table(domain, df.as_matrix()) - JanezD
噢,如果它不起作用:你的数据框看起来是什么样子?如果df.as_matrix().dtypeobject,那么Orange将无法接受它。您必须将分类数据转换为索引。 - JanezD

1
"""Pandas DataFrame↔Table conversion helpers"""
import numpy as np
import pandas as pd
from pandas.api.types import (
    is_categorical_dtype, is_object_dtype,
    is_datetime64_any_dtype, is_numeric_dtype,
)

from Orange.data import (
    Table, Domain, DiscreteVariable, StringVariable, TimeVariable,
    ContinuousVariable,
)

__all__ = ['table_from_frame', 'table_to_frame']


def table_from_frame(df,class_name, *, force_nominal=False):
    """
    Convert pandas.DataFrame to Orange.data.Table

    Parameters
    ----------
    df : pandas.DataFrame
    force_nominal : boolean
        If True, interpret ALL string columns as nominal (DiscreteVariable).

    Returns
    -------
    Table
    """

    def _is_discrete(s):
        return (is_categorical_dtype(s) or
                is_object_dtype(s) and (force_nominal or
                                        s.nunique() < s.size**.666))

    def _is_datetime(s):
        if is_datetime64_any_dtype(s):
            return True
        try:
            if is_object_dtype(s):
                pd.to_datetime(s, infer_datetime_format=True)
                return True
        except Exception:  # pylint: disable=broad-except
            pass
        return False

    # If df index is not a simple RangeIndex (or similar), put it into data
    if not (df.index.is_integer() and (df.index.is_monotonic_increasing or
                                       df.index.is_monotonic_decreasing)):
        df = df.reset_index()

    attrs, metas,calss_vars = [], [],[]
    X, M = [], []

    # Iter over columns
    for name, s in df.items():
        name = str(name)
        if name == class_name:
            discrete = s.astype('category').cat
            calss_vars.append(DiscreteVariable(name, discrete.categories.astype(str).tolist()))
            X.append(discrete.codes.replace(-1, np.nan).values)
        elif _is_discrete(s):
            discrete = s.astype('category').cat
            attrs.append(DiscreteVariable(name, discrete.categories.astype(str).tolist()))
            X.append(discrete.codes.replace(-1, np.nan).values)
        elif _is_datetime(s):
            tvar = TimeVariable(name)
            attrs.append(tvar)
            s = pd.to_datetime(s, infer_datetime_format=True)
            X.append(s.astype('str').replace('NaT', np.nan).map(tvar.parse).values)
        elif is_numeric_dtype(s):
            attrs.append(ContinuousVariable(name))
            X.append(s.values)
        else:
            metas.append(StringVariable(name))
            M.append(s.values.astype(object))

    return Table.from_numpy(Domain(attrs, calss_vars, metas),
                            np.column_stack(X) if X else np.empty((df.shape[0], 0)),
                            None,
                            np.column_stack(M) if M else None)

0

这个很好用

from Orange.data.pandas_compat import table_from_frame,table_to_frame

import pandas as pd


# read the input data into pandas data-frame 
df= table_to_frame(in_data)

# perform all data operations / wrangling 

# for example only few columns are required in output 
df = df[['Col1', 'Col2']]



# Final output 
out_data = table_from_frame(df)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接