1

It's about two, somehow connected problems. On the one hand it is about creating a proper subclass including a "change_DF"-method for pandas’ DataFrame (DF) object. (Prob1)
On the other hand it is about using the on_{property} and ObjectProperty from kivy the right way, so it detects changes in the mentioned DF. (Prob2)

Regarding Prob1 there are a few tutorials, e.g. from pandas itself:
https://pandas.pydata.org/pandas-docs/stable/development/extending.html#extending-subclassing-pandas
and some very rich of details, like:
http://devanla.com/case-for-inheriting-from-pandas-dataframe.html
Subclassing a Pandas DataFrame, updates? (@stackoverflow)
Unfortunately, content beyond my knowledge at this point (will have to study this class/super/property/*args/**kwargs/etc. stuff in detail... feel free to teach me in context of the given code piece)...!

Regarding Prob2 I feel a bit stuck with Prob1, because it seems to be a matter of what kivy is supposed to compare when checking if any «relevant» changes have been made to the "binded_kDF"... (well, seems as if I lack additionally knowledge about the principles of Kivy’s powerful EventDispatcher... even more to study and learn, obviously)

The piece of code in question, ready to run with Py3.7.4:

from kivy.app import App
import pandas as pd
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import ObjectProperty

class KivyDataFrame(pd.DataFrame):
    @property
    def _constructor(self):
        return KivyDataFrame
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
    def change_DF(self, data):
#        self.data = data                        # [Problem1] not how it works...
        self.update(pd.DataFrame(data))          # ...and this one only works given (same structure with) same col-names

class KivySheet(GridLayout):
    binded_kDF = ObjectProperty()
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.cols=2
        self.rows=2
        self.counter = 1
    def bind_kDF(self, kDF):
        self.binded_kDF = kDF
    def create_Sheet(self, *args):
        print(self.binded_kDF)
        for row in self.binded_kDF.to_numpy():
            for value in row:
                self.add_widget(Label(text=str(value)))
    def update_Sheet(self, *args):
        self.clear_widgets()
        self.create_Sheet()
    def on_binded_kDF(self, *args):               # [Problem2] not called automatically when pressing Button (calling it beyond initiation; counter=2)
        print("counter:", self.counter)
        self.update_Sheet()
        self.counter+=1

class AppFrame(BoxLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.orientation="vertical"
        self.kDF = KivyDataFrame(data={'col1': [1, 2], 'col2': [3, 4]})
        self.kSh = KivySheet()
        self.kSh.bind_kDF(self.kDF)
        self.add_widget(self.kSh)
        self.add_widget(Button(text="«change kDF»", on_press=lambda u:self.change_kDF()))
    def change_kDF(self):
        self.kDF.change_DF(data={'col1': [5, 6], 'col2': [7, 8]})  # change col-names and it doesn't work anymore (see Problem 1) 
#        self.kSh.update_Sheet()                                   # necessary to have the wanted changing-effect (why Problem 2 has to be solved)

class MySheetApp(App):
    def build(self):
        return AppFrame()
if __name__ == '__main__':
    MySheetApp().run()

For both problems I out-commented a line which didn't work (1) or is a workaround (2).
De-comment the latter and you will see the expected result (incl. “counter: 2”) I'm looking for.

Error/warning messages which I've recieved regarding Prob1 (using self.data=data):
UserWarning: Pandas doesn't allow columns to be created via a new attribute name - see https://pandas.pydata.org/pandas-docs/stable/indexing.html#attribute-access

...and regarding Prob2:
[WARNING] [Property ] Value comparison failed for with "init() takes 1 positional argument but 2 were given". Consider setting force_dispatch to True to avoid this.

Clemens
  • 53
  • 6
  • If I've understood correctly what you're attempting, I'm not sure it will be very practical to achieve, unless pandas already provides a generic update callback mechanism to hook into. – inclement Oct 27 '19 at 23:01
  • Thanks for your answer, would much appreciate a few details to know what to look for (inside of pandas\core\frame.py !?)... – Clemens Oct 28 '19 at 13:38
  • And what could be the non-“practical [way] to achieve” it, anyway? I was considering inside of "change_DF()" a new approach with: `self = KivyDataFrame(data=data)` Unfortunately, this seems to just create a new kDF-instance, while the old stays unchanged, with which the KivySheet is binded. Well, not so convenient, neither... – Clemens Oct 28 '19 at 13:39
  • 1
    I'm not really clear exactly what you want to achieve. Is it to have kivy events fire automatically when you modify your DataFrame? – inclement Oct 28 '19 at 18:28
  • Yes, that very properly summerizes the whole issue. Including how to (specifically) change the DF within the subclass so the kivy events (regarding the binded kivy widgets/"Sheets") fire automatically. – Clemens Oct 30 '19 at 11:46
  • This is a "write code to do that" type question. Unless DataFrames have some generic event system to bind to when things changed, you must intercept all modifications to the dataframe and write code to fire off the kivy events you want. This is essentially how ListProperty etc. are implemented in the background, but it's more straightforward for lists since there's a smaller number of ways to interact with them. – inclement Oct 30 '19 at 21:21

0 Answers0