Thuộc tính Setter cho lớp con của Pandas DataFrame


9

Tôi đang cố gắng thiết lập một lớp con pd.DataFramecó hai đối số bắt buộc khi khởi tạo ( grouptimestamp_col). Tôi muốn chạy xác thực trên các đối số đó grouptimestamp_colvì vậy tôi có một phương thức setter cho từng thuộc tính. Tất cả điều này hoạt động cho đến khi tôi cố gắng để set_index()có được TypeError: 'NoneType' object is not iterable. Dường như không có đối số nào được truyền cho hàm setter của tôi trong test_set_indextest_assignment_with_indexed_obj. Nếu tôi thêm if g == None: returnvào hàm setter của mình, tôi có thể vượt qua các trường hợp thử nghiệm nhưng không nghĩ đó là giải pháp phù hợp.

Làm thế nào tôi nên thực hiện xác nhận thuộc tính cho các đối số cần thiết này?

Dưới đây là lớp học của tôi:

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

Dưới đây là các trường hợp thử nghiệm của tôi:

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']

1
Nếu Nonelà một giá trị không hợp lệ cho grouptài sản, bạn không nên tăng ValueError?
chepner

1
Bạn đúng đó Nonelà một giá trị không hợp lệ, đó là lý do tại sao tôi không thích câu lệnh if. Nhưng thêm rằng Không làm cho nó vượt qua các bài kiểm tra. Tôi đang tìm cách khắc phục vấn đề này mà không cần câu lệnh if if.
trang

2
Các setter nên nâng a ValueError. Vấn đề là tìm ra cái gì đang cố gắng đặt groupthuộc tính Noneở vị trí đầu tiên.
chepner

@chepner vâng, chính xác.
trang

Có lẽ gói Pandas Flavour có thể giúp đỡ.
Mykola Zotko

Câu trả lời:


3

Các set_index()phương pháp sẽ gọi self.copy()trong nội bộ để tạo ra một bản sao của đối tượng DataFrame của bạn (xem mã nguồn ở đây ), trong đó sử dụng phương pháp xây dựng tùy chỉnh của bạn, _internal_ctor()để tạo ra các đối tượng mới ( nguồn ). Lưu ý rằng self._constructor()nó giống hệt với self._internal_ctor(), đây là một phương thức nội bộ phổ biến cho gần như tất cả các lớp gấu trúc để tạo các thể hiện mới trong các hoạt động như sao chép sâu hoặc cắt. Vấn đề của bạn thực sự bắt nguồn từ chức năng này:

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)

Tôi đoán bạn đã sao chép mã này từ vấn đề github . Các dòng kwargs["**"] = Nonenói rõ ràng với hàm tạo để đặt Nonethành cả hai grouptimestamp_col. Cuối cùng, setter / validator nhận Nonelàm giá trị mới và đưa ra lỗi.

Do đó, bạn nên đặt một giá trị chấp nhận được grouptimestamp_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)

Sau đó, bạn có thể xóa các if g == None: returndòng trong trình xác nhận.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.