Setter Properti untuk Subclass dari Pandas DataFrame

9

Saya mencoba untuk membuat subkelas pd.DataFrameyang memiliki dua argumen yang diperlukan ketika menginisialisasi ( groupdan timestamp_col). Saya ingin menjalankan validasi pada argumen itu groupdan timestamp_col, jadi saya punya metode penyetel untuk masing-masing properti. Ini semua bekerja sampai saya coba set_index()dan dapatkan TypeError: 'NoneType' object is not iterable. Tampaknya tidak ada argumen yang diteruskan ke fungsi setter saya di test_set_indexdan test_assignment_with_indexed_obj. Jika saya menambah if g == None: returnfungsi setter saya, saya bisa lulus ujian tetapi tidak berpikir itu adalah solusi yang tepat.

Bagaimana saya harus menerapkan validasi properti untuk argumen yang diperlukan ini?

Di bawah ini adalah kelas saya:

import pandas as pd
import numpy as np


class HistDollarGains(pd.DataFrame):
    @property
    def _constructor(self):
        return HistDollarGains._internal_ctor

    _metadata = ["group", "timestamp_col", "_group", "_timestamp_col"]

    @classmethod
    def _internal_ctor(cls, *args, **kwargs):
        kwargs["group"] = None
        kwargs["timestamp_col"] = None
        return cls(*args, **kwargs)

    def __init__(
        self,
        data,
        group,
        timestamp_col,
        index=None,
        columns=None,
        dtype=None,
        copy=True,
    ):
        super(HistDollarGains, self).__init__(
            data=data, index=index, columns=columns, dtype=dtype, copy=copy
        )

        self.group = group
        self.timestamp_col = timestamp_col

    @property
    def group(self):
        return self._group

    @group.setter
    def group(self, g):
        if g == None:
            return

        if isinstance(g, str):
            group_list = [g]
        else:
            group_list = g

        if not set(group_list).issubset(self.columns):
            raise ValueError("Data does not contain " + '[' + ', '.join(group_list) + ']')
        self._group = group_list

    @property
    def timestamp_col(self):
        return self._timestamp_col

    @timestamp_col.setter
    def timestamp_col(self, t):
        if t == None:
            return
        if not t in self.columns:
            raise ValueError("Data does not contain " + '[' + t + ']')
        self._timestamp_col = t

Berikut ini adalah kasus pengujian saya:

import pytest

import pandas as pd
import numpy as np

from myclass import *


@pytest.fixture(scope="module")
def sample():
    samp = pd.DataFrame(
        [
            {"timestamp": "2020-01-01", "group": "a", "dollar_gains": 100},
            {"timestamp": "2020-01-01", "group": "b", "dollar_gains": 100},
            {"timestamp": "2020-01-01", "group": "c", "dollar_gains": 110},
            {"timestamp": "2020-01-01", "group": "a", "dollar_gains": 110},
            {"timestamp": "2020-01-01", "group": "b", "dollar_gains": 90},
            {"timestamp": "2020-01-01", "group": "d", "dollar_gains": 100},
        ]
    )

    return samp

@pytest.fixture(scope="module")
def sample_obj(sample):
    return HistDollarGains(sample, "group", "timestamp")

def test_constructor_without_args(sample):
    with pytest.raises(TypeError):
        HistDollarGains(sample)


def test_constructor_with_string_group(sample):
    hist_dg = HistDollarGains(sample, "group", "timestamp")
    assert hist_dg.group == ["group"]
    assert hist_dg.timestamp_col == "timestamp"


def test_constructor_with_list_group(sample):
    hist_dg = HistDollarGains(sample, ["group", "timestamp"], "timestamp")

def test_constructor_with_invalid_group(sample):
    with pytest.raises(ValueError):
        HistDollarGains(sample, "invalid_group", np.random.choice(sample.columns))

def test_constructor_with_invalid_timestamp(sample):
    with pytest.raises(ValueError):
        HistDollarGains(sample, np.random.choice(sample.columns), "invalid_timestamp")

def test_assignment_with_indexed_obj(sample_obj):
    b = sample_obj.set_index(sample_obj.group + [sample_obj.timestamp_col])

def test_set_index(sample_obj):
    # print(isinstance(a, pd.DataFrame))
    assert sample_obj.set_index(sample_obj.group + [sample_obj.timestamp_col]).index.names == ['group', 'timestamp']
cpage
sumber
1
Jika Nonenilai tidak valid untuk groupproperti, bukankah seharusnya Anda menaikkan ValueError?
chepner
1
Anda benar itu Noneadalah nilai yang tidak valid, itulah sebabnya saya tidak suka pernyataan if. Tetapi menambahkan bahwa Tidak ada yang membuatnya lulus ujian. Saya mencari cara untuk memperbaikinya dengan benar tanpa pernyataan Tidak ada jika.
halaman
2
Setter harus menaikkan a ValueError. Masalahnya adalah mencari tahu apa yang mencoba mengatur groupatribut untuk Nonedi tempat pertama.
chepner
@ partner ya, tepatnya.
halaman
Mungkin paket Pandas Flavour dapat membantu.
Mykola Zotko

Jawaban:

3

The set_index()Metode akan memanggil self.copy()secara internal untuk membuat salinan objek DataFrame Anda (lihat kode sumber di sini ), di dalam yang menggunakan metode konstruktor disesuaikan Anda, _internal_ctor(), untuk membuat objek baru ( sumber ). Catatan yang self._constructor()identik dengan self._internal_ctor(), yang merupakan metode internal umum untuk hampir semua kelas panda untuk membuat instance baru selama operasi seperti deep-copy atau slicing. Masalah Anda sebenarnya berasal dari fungsi ini:

class HistDollarGains(pd.DataFrame):
    ...
    @classmethod
    def _internal_ctor(cls, *args, **kwargs):
        kwargs["group"]         = None
        kwargs["timestamp_col"] = None
        return cls(*args, **kwargs) # this is equivalent to calling
                                    # HistDollarGains(data, group=None, timestamp_col=None)

Saya kira Anda menyalin kode ini dari masalah github . Garis-garis kwargs["**"] = Nonesecara eksplisit memberitahu konstruktor untuk mengatur Nonekeduanya groupdan timestamp_col. Akhirnya setter / validator mendapatkan Nonesebagai nilai baru dan meningkatkan kesalahan.

Karena itu, Anda harus menetapkan nilai yang dapat diterima untuk groupdan timestamp_col.

    @classmethod
    def _internal_ctor(cls, *args, **kwargs):
        kwargs["group"]         = []
        kwargs["timestamp_col"] = 'timestamp' # or whatever name that makes your validator happy
        return cls(*args, **kwargs)

Kemudian Anda dapat menghapus if g == None: returngaris di validator.

gdlmx
sumber