Skip to content

MeltProxyModel

Qt Base Class: QAbstractProxyModel

Signature: QAbstractProxyModel(self, parent: Optional[PySide6.QtCore.QObject] = None) -> None

Base classes

Name Children Inherits
AbstractProxyModel
prettyqt.core.abstractproxymodel

⋔ Inheritance diagram

graph TD
  1473290735488["itemmodels.MeltProxyModel"]
  1473299891152["core.AbstractProxyModel"]
  1473299903840["core.AbstractProxyModelMixin"]
  1473299890176["core.AbstractItemModelMixin"]
  1473299815024["core.ObjectMixin"]
  140713234304496["builtins.object"]
  1473289061840["QtCore.QAbstractProxyModel"]
  1473289050128["QtCore.QAbstractItemModel"]
  1473288842240["QtCore.QObject"]
  1473291690208["Shiboken.Object"]
  1473299891152 --> 1473290735488
  1473299903840 --> 1473299891152
  1473299890176 --> 1473299903840
  1473299815024 --> 1473299890176
  140713234304496 --> 1473299815024
  1473289061840 --> 1473299891152
  1473289050128 --> 1473289061840
  1473288842240 --> 1473289050128
  1473291690208 --> 1473288842240
  140713234304496 --> 1473291690208

🛈 DocStrings

Bases: AbstractProxyModel

Proxy model to unpivot a table from wide format to long format.

Works same way as pandas.melt.

app = widgets.app()
data = dict(
    first=["John", "Mary"],
    last=["Doe", "Bo"],
    height=[5.5, 6.0],
    weight=[130, 150],
)
model = gui.StandardItemModel.from_dict(data)
table = widgets.TableView()
table.set_model(model)
# table.proxifier.melt(id_columns=[0, 1])
table.show()
Image title

app = widgets.app()
data = dict(
    first=["John", "Mary"],
    last=["Doe", "Bo"],
    height=[5.5, 6.0],
    weight=[130, 150],
)
model = gui.StandardItemModel.from_dict(data)
table = widgets.TableView()
table.set_model(model)
table.proxifier.melt(id_columns=[0, 1])
table.show()
Image title

table.proxifier.melt(id_columns=[0, 1])
# equals
proxy = itemmodels.MeltProxyModel(id_columns=[0, 1])
proxy.set_source_model(table.model())
table.set_model(proxy)
Source code in prettyqt\itemmodels\proxies\meltproxymodel.py
class MeltProxyModel(core.AbstractProxyModel):
    """Proxy model to unpivot a table from wide format to long format.

    Works same way as [pandas.melt](https://shorturl.at/bhGI3).

    === "Without proxy"

        ```py
        app = widgets.app()
        data = dict(
            first=["John", "Mary"],
            last=["Doe", "Bo"],
            height=[5.5, 6.0],
            weight=[130, 150],
        )
        model = gui.StandardItemModel.from_dict(data)
        table = widgets.TableView()
        table.set_model(model)
        # table.proxifier.melt(id_columns=[0, 1])
        table.show()

        ```
        <figure markdown>
          ![Image title](../../images/meltproxymodel_before.png)
        </figure>

    === "With proxy"

        ```py
        app = widgets.app()
        data = dict(
            first=["John", "Mary"],
            last=["Doe", "Bo"],
            height=[5.5, 6.0],
            weight=[130, 150],
        )
        model = gui.StandardItemModel.from_dict(data)
        table = widgets.TableView()
        table.set_model(model)
        table.proxifier.melt(id_columns=[0, 1])
        table.show()
        ```
        <figure markdown>
          ![Image title](../../images/meltproxymodel_after.png)
        </figure>

    ```py
    table.proxifier.melt(id_columns=[0, 1])
    # equals
    proxy = itemmodels.MeltProxyModel(id_columns=[0, 1])
    proxy.set_source_model(table.model())
    table.set_model(proxy)
    ```
    """

    ID = "melt"
    ICON = "mdi6.table-pivot"

    def __init__(
        self,
        id_columns: list[int],
        var_name: str = "Variable",
        value_name: str = "Value",
        **kwargs,
    ):
        self._id_columns = id_columns
        self._var_name = var_name
        self._value_name = value_name
        super().__init__(**kwargs)

    @property
    def value_columns(self) -> list[int]:
        colcount = self.sourceModel().columnCount()
        return [i for i in range(colcount) if i not in self._id_columns]

    def rowCount(self, index: core.ModelIndex | None = None) -> int:
        return self.sourceModel().rowCount() * len(self.value_columns)

    def columnCount(self, parent: core.ModelIndex | None = None) -> int:
        parent = parent or core.ModelIndex()
        return 0 if self.sourceModel() is None else len(self._id_columns) + 2

    def is_source_column(self, column: int) -> bool:
        return 0 <= column < self.columnCount() - 2

    def is_variable_column(self, column: int) -> bool:
        return column == self.columnCount() - 2

    def is_value_column(self, column: int) -> bool:
        return column == self.columnCount() - 1

    def data(
        self,
        index: core.ModelIndex,
        role: constants.ItemDataRole = constants.DISPLAY_ROLE,
    ):
        column = index.column()
        if self.is_variable_column(column) and role == constants.DISPLAY_ROLE:
            col = index.row() // self.sourceModel().rowCount()
            return self.sourceModel().headerData(
                self.value_columns[col], constants.HORIZONTAL
            )
        return super().data(index, role)

    def headerData(
        self,
        section: int,
        orientation: constants.Orientation,
        role: constants.ItemDataRole = constants.DISPLAY_ROLE,
    ):
        if orientation != constants.HORIZONTAL:
            return str(section)
        if self.is_variable_column(section):
            return self._var_name or "Variable"
        elif self.is_value_column(section):
            return self._value_name or "Value"
        else:
            section = self.get_source_column_for_proxy_column(section)
            return self.sourceModel().headerData(section, orientation, role)

    def index(
        self, row: int, column: int, parent: core.ModelIndex | None = None
    ) -> core.ModelIndex:
        # TODO: broken
        parent = parent or core.ModelIndex()
        if column not in self._id_columns:
            return self.createIndex(row, column, core.ModelIndex())
        col_pos = self.get_source_column_for_proxy_column(column)
        row_pos = row % self.sourceModel().rowCount()
        return self.sourceModel().index(row_pos, col_pos, parent)

    def parent(self, index: core.ModelIndex):
        if not self.is_source_column(index.column()):
            return core.ModelIndex()
        return self.sourceModel().parent(index)

    def get_source_column_for_proxy_column(self, column: int) -> int:
        return self._id_columns.index(column)

    def get_proxy_column_for_source_column(self, column: int) -> int:
        return column - sum(column > col for col in self._id_columns)

    def mapToSource(self, proxy_index: core.ModelIndex) -> core.ModelIndex:
        source = self.sourceModel()
        if source is None or not proxy_index.isValid():
            return core.ModelIndex()
        row, column = proxy_index.row(), proxy_index.column()
        row_count = source.rowCount()
        if self.is_variable_column(column):
            return core.ModelIndex()
        elif self.is_value_column(column):
            source_col = self.value_columns[row // row_count]
            source_row = row % row_count
            return source.index(source_row, source_col, core.ModelIndex())
        else:
            source_col = self.get_source_column_for_proxy_column(column)
            source_row = row % row_count
            return source.index(source_row, source_col)

    def mapFromSource(self, source_index: core.ModelIndex) -> core.ModelIndex:
        # TODO: this is still broken.
        source = self.sourceModel()
        if source is None or not source_index.isValid():
            return core.ModelIndex()
        row, col = source_index.row(), source_index.column()
        # we can only really return a corresponding index for the value columns.
        # Var column is completely virtual and the id columns would have multiple
        # source indexes which correspond to the proxy index.
        if col not in self.value_columns:
            return core.ModelIndex()
        # TODO: convert row / col
        return source.index(row, col, core.ModelIndex())

    def get_id_columns(self) -> list[int]:
        """Get list of identifier columns."""
        return self._id_columns

    def set_id_columns(self, columns: list[int]):
        """Set identifier variable columns."""
        with self.reset_model():
            self._id_columns = columns

    def get_var_name(self) -> str:
        """Get variable column header."""
        return self._var_name

    def set_var_name(self, name: str):
        """Set header for variable column."""
        self._var_name = name
        section = self.columnCount() - 2
        self.headerDataChanged.emit(constants.HORIZONTAL, section, section)

    def get_value_name(self) -> str:
        """Get value column header."""
        return self._value_name

    def set_value_name(self, name: str):
        """Set header for value column."""
        self._value_name = name
        section = self.columnCount() - 1
        self.headerDataChanged.emit(constants.HORIZONTAL, section, section)

    id_columns = core.Property(
        list,
        get_id_columns,
        set_id_columns,
        doc="Columns to use as identifier variables",
    )
    var_name = core.Property(
        str,
        get_var_name,
        set_var_name,
        doc="Header for variable column",
    )
    value_name = core.Property(
        str,
        get_value_name,
        set_value_name,
        doc="Header for value column",
    )

get_id_columns() -> list[int]

Get list of identifier columns.

Source code in prettyqt\itemmodels\proxies\meltproxymodel.py
def get_id_columns(self) -> list[int]:
    """Get list of identifier columns."""
    return self._id_columns

get_value_name() -> str

Get value column header.

Source code in prettyqt\itemmodels\proxies\meltproxymodel.py
def get_value_name(self) -> str:
    """Get value column header."""
    return self._value_name

get_var_name() -> str

Get variable column header.

Source code in prettyqt\itemmodels\proxies\meltproxymodel.py
def get_var_name(self) -> str:
    """Get variable column header."""
    return self._var_name

set_id_columns(columns: list[int])

Set identifier variable columns.

Source code in prettyqt\itemmodels\proxies\meltproxymodel.py
def set_id_columns(self, columns: list[int]):
    """Set identifier variable columns."""
    with self.reset_model():
        self._id_columns = columns

set_value_name(name: str)

Set header for value column.

Source code in prettyqt\itemmodels\proxies\meltproxymodel.py
def set_value_name(self, name: str):
    """Set header for value column."""
    self._value_name = name
    section = self.columnCount() - 1
    self.headerDataChanged.emit(constants.HORIZONTAL, section, section)

set_var_name(name: str)

Set header for variable column.

Source code in prettyqt\itemmodels\proxies\meltproxymodel.py
def set_var_name(self, name: str):
    """Set header for variable column."""
    self._var_name = name
    section = self.columnCount() - 2
    self.headerDataChanged.emit(constants.HORIZONTAL, section, section)

⌗ Property table

Qt Property Type Doc
objectName QString
sourceModel QAbstractItemModel
id_columns QVariantList Columns to use as identifier variables
var_name QString Header for variable column
value_name QString Header for value column