Форматирование количества тиков Matplotlib (в тысячах). Ошибка типа: ‘

#python #matplotlib #formatting #kivy #typeerror

Вопрос:

Я создаю приложение, которое показывает продажи, достигнутые продавцом в разные периоды времени. Есть 4 флажка с разными периодами времени (День, Неделя, Месяц, Год). При нажатии на любой период будет показан график временной линии matplotlib.

Моя борьба начинается, когда я пытаюсь отформатировать y-тики как тысячи. Я использую format(value, ',') команду. Однако я продолжаю получать TypeError: '<' not supported between instances of 'str' and 'int' . После исследования я обнаружил, что предложение состоит в том, чтобы явно указать значение как int. Я пробовал это разными способами, но безуспешно. Например, если я добавлю int() для преобразования из строки в int: int(format(divider * 1, ',')) я получу ошибку ValueError: invalid literal for int() with base 10: '150,000'

Код для минимального воспроизводимого примера выглядит следующим образом:

Код на Python:

 from kivymd.app import MDApp
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import ButtonBehavior
from kivy.uix.label import Label
import matplotlib
import matplotlib.pyplot as plt
from datetime import datetime
matplotlib.use("module://kivy.garden.matplotlib.backend_kivy")
from kivy.garden.matplotlib import FigureCanvasKivyAgg


class LabelButton(ButtonBehavior, Label):
    pass



class MainMenuWindow(Screen):
    pass



class DashboardWindow(Screen):
    # Valores Iniciales
    """ PENDIENTE: HACER QUE LOS VALORES INICIALES VARÍEN EN FUNCIÓN DEL VENDEDOR Y SUS NÚMEROS """
    meta_mensual = 300000
    venta_diaria_ = 2500
    venta_semanal_ = 50000
    venta_mensual_ = 230000
    venta_anual_ = 2500000
    menu = None
    number_items_mdlist = 0
    minutes_items_mdlist = 0
    duracion_actividades_list = []
    picture_filepath_list = []
    image_status_list = []
    periodo_selec = ''

    # Ventas
    def diario_label_press(self, value):
        if not self.ids.periodo_diario_.active:
            self.ids.periodo_diario_.active = True
        else:
            self.ids.periodo_diario_.active = False

    def semanal_label_press(self, value):
        if not self.ids.periodo_semanal_.active:
            self.ids.periodo_semanal_.active = True
        else:
            self.ids.periodo_semanal_.active = False

    def mensual_label_press(self, value):
        if not self.ids.periodo_mensual_.active:
            self.ids.periodo_mensual_.active = True
        else:
            self.ids.periodo_mensual_.active = False

    def anual_label_press(self, value):
        if not self.ids.periodo_anual_.active:
            self.ids.periodo_anual_.active = True
        else:
            self.ids.periodo_anual_.active = False

    def switch_checkbox(self):
        if self.ids.periodo_diario_.active:
            # Meta de Ventas
            meta_diaria = int(self.meta_mensual) / 30
            meta_diaria_format = '{:,}'.format(meta_diaria)
            self.ids.meta_label.text = "$ "   str(meta_diaria_format)   "0"
            # Monto de Venta
            venta = int(self.venta_diaria_)
            venta = '{:,}'.format(venta)
            self.ids.monto_venta.text = "$ "   str(venta)   ".00"
            # Porcentaje de Meta
            porcentaje = float(self.venta_diaria_ / meta_diaria)
            porcentaje_format = "{0:.1%}".format(porcentaje)
            self.ids.porcentaje_meta.text = str(porcentaje_format)
            self.periodo_selec = 'Diario'

        elif self.ids.periodo_semanal_.active:
            # Meta de Ventas
            meta_semanal = int(self.meta_mensual) / 4
            meta_semanal_format = '{:,}'.format(meta_semanal)
            self.ids.meta_label.text = "$ "   str(meta_semanal_format)   "0"
            # Monto de Venta
            venta = int(self.venta_semanal_)
            venta = '{:,}'.format(venta)
            self.ids.monto_venta.text = "$ "   str(venta)   ".00"
            # Porcentaje de Meta
            porcentaje = float(self.venta_semanal_ / meta_semanal)
            porcentaje_format = "{0:.1%}".format(porcentaje)
            self.ids.porcentaje_meta.text = str(porcentaje_format)
            self.periodo_selec = 'Semanal'

        elif self.ids.periodo_mensual_.active:
            # Meta de Ventas
            meta_mensual = int(self.meta_mensual) * 1
            meta_mensual_format = '{:,}'.format(self.meta_mensual)
            self.ids.meta_label.text = "$ "   str(meta_mensual_format)   ".00"
            # Monto de Venta
            venta = int(self.venta_mensual_)
            venta = '{:,}'.format(venta)
            self.ids.monto_venta.text = "$ "   str(venta)   ".00"
            # Porcentaje de Meta
            porcentaje = float(self.venta_mensual_ / self.meta_mensual)
            porcentaje_format = "{0:.1%}".format(porcentaje)
            self.ids.porcentaje_meta.text = str(porcentaje_format)
            self.periodo_selec = 'Mensual'

        elif self.ids.periodo_anual_.active:
            # Meta de Ventas
            meta_anual = int(self.meta_mensual) * 12
            meta_anual_format = '{:,}'.format(meta_anual)
            self.ids.meta_label.text = "$ "   str(meta_anual_format)   ".00"
            # Monto de Venta
            venta = int(self.venta_anual_)
            venta = '{:,}'.format(venta)
            self.ids.monto_venta.text = "$ "   str(venta)   ".00"
            # Porcentaje de Meta
            porcentaje = float(self.venta_anual_ / meta_anual)
            porcentaje_format = "{0:.1%}".format(porcentaje)
            self.ids.porcentaje_meta.text = str(porcentaje_format)
            self.periodo_selec = 'Anual'

        elif not self.ids.periodo_diario_.active and not self.ids.periodo_semanal_.active and 
                not self.ids.periodo_mensual_.active and not self.ids.periodo_anual_.active:
            # Meta de Ventas
            self.ids.meta_label.text = "Seleccione una opción"
            self.ids.monto_venta.text = "$ 0.00"
            self.ids.porcentaje_meta.text = "0.00 %"

        self.actualizar_timeline_ventas(self.periodo_selec)

    def actualizar_timeline_ventas(self, selec):
        now = datetime.now()

        dash = MDApp.get_running_app().root.get_screen('dash')
        plt.clf()
        plt.style.use('seaborn')

        mes = ''
        if now.month == 1:
            mes = 'Enero'
        elif now.month == 2:
            mes = 'Febrero'
        elif now.month == 3:
            mes = 'Marzo'
        elif now.month == 4:
            mes = 'Abril'
        elif now.month == 5:
            mes = 'Mayo'
        elif now.month == 6:
            mes = 'Junio'
        elif now.month == 7:
            mes = 'Julio'
        elif now.month == 8:
            mes = 'Agosto'
        elif now.month == 9:
            mes = 'Septiembre'
        elif now.month == 10:
            mes = 'Octubre'
        elif now.month == 11:
            mes = 'Noviembre'
        elif now.month == 12:
            mes = 'Diciembre'

        # Línea de tiempo del día
        if self.ids.periodo_diario_.active:
            dia_semana = ''
            if now.weekday() == 0:
                dia_semana = 'Lunes'
            elif now.weekday() == 1:
                dia_semana = 'Martes'
            elif now.weekday() == 2:
                dia_semana = 'Miércoles'
            elif now.weekday() == 3:
                dia_semana = 'Jueves'
            elif now.weekday() == 4:
                dia_semana = 'Viernes'
            elif now.weekday() == 5:
                dia_semana = 'Sábado'
            elif now.weekday() == 6:
                dia_semana = 'Domingo'

            dash.ids.momento_seleccionado.text = f'Hoy, {dia_semana.lower()}, {now.day} de {mes.lower()} del {now.year}'
            dash.ids.timeline_container.clear_widgets()

            hora = ['9:00 a.m.', '10:00 a.m.', '11:00 a.m.', '12:00 p.m.', '1:00 p.m.', '2:00 p.m.', '3:00 p.m.',
                    '4:00 p.m.', '5:00 p.m.', '6:00 p.m.']
            venta = [0, 1500, 10000, 17000, 2600, 0, 0, 2710, 7500, 0]

            divider = max(venta) / 4
            plt.yticks([0, divider * 1, divider * 2, divider * 3, divider * 4])

            plt.plot(hora, venta, color='blue', marker='D')
            plt.xticks(['9:00 a.m.', '12:00 p.m.', '3:00 p.m.', '6:00 p.m.'])
            plt.grid(True)
            dash.ids.timeline_container.add_widget(FigureCanvasKivyAgg(figure=plt.gcf(), size_hint=(0.95, 0.95),
                                                                       pos_hint={'center_x': 0.5, 'top': 1}))

        # Línea de tiempo Semanal
        elif self.ids.periodo_semanal_.active:
            dash.ids.momento_seleccionado.text = f'Semana {datetime.date(now).isocalendar()[1]} del {now.year}'
            dash.ids.timeline_container.clear_widgets()

            dia = ['Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab', 'Dom']
            venta = [10000, 12000, 22000, 17000, 26000, 5000, 0]

            divider = max(venta) / 4
            plt.yticks([0, divider * 1, divider * 2, divider * 3, divider * 4])

            plt.plot(dia, venta, color='yellow', marker='o')
            plt.grid(True)
            dash.ids.timeline_container.add_widget(FigureCanvasKivyAgg(figure=plt.gcf(), size_hint=(0.95, 0.95),
                                                                       pos_hint={'center_x': 0.5, 'top': 1}))

        # Línea de tiempo mensual
        elif self.ids.periodo_mensual_.active:

            dash.ids.momento_seleccionado.text = f'{mes} {now.year}'
            dash.ids.timeline_container.clear_widgets()

            now = datetime.now()
            curr_month = now.now().month
            if int(curr_month) == 1 or int(curr_month) == 3 or int(curr_month) == 5 or int(curr_month) == 7 
                    or int(curr_month) == 8 or int(curr_month) == 10 or int(curr_month) == 12:
                number_days = 31
            elif int(curr_month) == 4 or int(curr_month) == 6 or int(curr_month) == 9 or int(curr_month) == 11:
                number_days = 30
            else:
                number_days = 28

            import random
            dia = []
            venta = []
            day = 1
            for i in range(number_days):
                dia.append(day)
                venta.append(random.randrange(150000, 750000))
                day  = 1

            divider = max(venta) / 4
            plt.yticks([0, divider * 1, divider * 2, divider * 3, divider * 4])
            plt.plot(dia, venta, color='red', marker='s')
            plt.grid(True)
            dash.ids.timeline_container.add_widget(FigureCanvasKivyAgg(figure=plt.gcf(), size_hint=(0.95, 0.95),
                                                                       pos_hint={'center_x': 0.5, 'top': 1}))
        # Línea de tiempo Anual
        elif self.ids.periodo_anual_.active:
            dash.ids.momento_seleccionado.text = str(now.year)
            dash.ids.timeline_container.clear_widgets()

            mes = ['Ene', 'Feb', 'Mar', 'Abr', 'May', 'Jun', 'Jul', 'Ago', 'Sep', 'Oct', 'Nov', 'Dic']
            venta = [100000, 120000, 130000, 170000, 260000, 220000, 243000, 271000, 274000, 320000, 430000, 600000]

            divider = int(max(venta) / 4)
            plt.yticks([int(0), format(divider * 1, ','), format(divider * 2, ','), format(divider * 3, ','),
                        format(divider * 4, ',')])

            plt.plot(mes, venta, color='green', marker='^')
            plt.grid(True)
            dash.ids.timeline_container.add_widget(FigureCanvasKivyAgg(figure=plt.gcf(), size_hint=(0.95, 0.95),
                                                                       pos_hint={'center_x': 0.5, 'top': 1}))

        else:
            dash.ids.momento_seleccionado.text = 'Líneas de tiempo'
            dash.ids.timeline_container.clear_widgets()
            dash.ids.timeline_container.add_widget(self.ids.label_timeline_vacio)


class WindowManager(ScreenManager):
    pass


class Linegraph(MDApp):

    def build(self):
        self.theme_cls.primary_palette = "Teal"
        return WindowManager()

if __name__ == "__main__":
    Linegraph().run()
 

Код КВ:

 <WindowManager>:
    id: screen_manager

    DashboardWindow:
        id: dash
        name: 'dash'

<DashboardWindow>:
    id: dash
    name:'dash'

    MDBoxLayout:
        orientation: 'vertical'
        valign: 'middle'
        padding: '10dp'

        MDLabel:
            text: "Nombre del Vendedor"
            size_hint_y: 0.15
            font_size: (root.width**2   root.height**2) / 12.75**4
            valign: 'middle'
            bold: True
        MDBoxLayout:
            orientation: 'horizontal'
            spacing: '5dp'
            size_hint_y: 0.15
            MDLabel:
                text: "Meta de venta:"
                valign: 'middle'
                font_size: (root.width**2   root.height**2) / 14.25**4
            MDLabel:
                id: meta_label
                text: 'Seleccione una opcion'
                valign: 'middle'
                font_size: (root.width**2   root.height**2) / 14.25**4
                bold: True

        MDBoxLayout:
            orientation: 'horizontal'
            spacing: '5dp'
            size_hint_y: 0.20
            MDCheckbox:
                group: 'periodo_venta'
                id: periodo_diario_
                size_hint: None, None
                size: dp(50), dp(50)
                pos_hint: {"x":0, "center_y":0.5}
                on_active:
                    root.switch_checkbox()
            LabelButton:
                text: 'Diario'
                color: 0, 0, 0, 1
                halign: 'left'
                valign: 'middle'
                text_size: self.size
                on_release: root.diario_label_press(root.ids.periodo_diario_.active)

            MDCheckbox:
                group: 'periodo_venta'
                id: periodo_semanal_
                size_hint: None, None
                size: dp(50), dp(50)
                pos_hint: {"x":0, "center_y":0.5}
                on_active:
                    root.switch_checkbox()
            LabelButton:
                text: 'Semanal'
                color: 0, 0, 0, 1
                halign: 'left'
                valign: 'middle'
                text_size: self.size
                on_release: root.semanal_label_press(root.ids.periodo_semanal_.active)

            MDCheckbox:
                group: 'periodo_venta'
                id: periodo_mensual_
                size_hint: None, None
                size: dp(50), dp(50)
                pos_hint: {"x":0, "center_y":0.5}
                on_active:
                    root.switch_checkbox()
            LabelButton:
                text: 'Mensual'
                color: 0, 0, 0, 1
                halign: 'left'
                valign: 'middle'
                text_size: self.size
                on_release: root.mensual_label_press(root.ids.periodo_mensual_.active)

            MDCheckbox:
                group: 'periodo_venta'
                id: periodo_anual_
                size_hint: None, None
                size: dp(50), dp(50)
                pos_hint: {"x":0, "center_y":0.5}
                on_active:
                    root.switch_checkbox()
            LabelButton:
                text: 'Anual'
                color: 0, 0, 0, 1
                halign: 'left'
                valign: 'middle'
                text_size: self.size
                on_release: root.anual_label_press(root.ids.periodo_anual_.active)

        MDBoxLayout:
            orientation: 'horizontal'
            padding: 0, '10dp', 0, 0
            spacing: '20dp'
            size_hint: 1, 0.25
            MDCard:
                orientation: 'vertical'
                padding: '10dp'
                size_hint: 0.4, 1
                radius: [16, ]
                md_bg_color: [1, 1, 1, 0.85]
                MDLabel:
                    id: monto_venta
                    text: "$ 0.00"
                    theme_text_color: "Custom"
                    text_color: 0, 0, 0, 1
                    font_style: 'H1'
                    halign: 'center'
                    font_size: (root.width**2   root.height**2) / 13**4
                MDLabel:
                    text: "Monto Vendido"
                    theme_text_color: "Custom"
                    text_color: 0, 0, 0, 1
                    font_style: 'Subtitle2'
                    halign: 'center'

            MDCard:
                size_hint: 0.4, 1
                orientation: 'vertical'
                padding: '10dp'
                radius: [16, ]
                md_bg_color: [1, 1, 1, 0.85]
                MDLabel:
                    id: porcentaje_meta
                    text: '0.00 %'
                    theme_text_color: "Custom"
                    text_color: 0, 0, 0, 1
                    font_style: 'H1'
                    halign: 'center'
                    font_size: (root.width**2   root.height**2) / 13**4
                MDLabel:
                    text: "% de meta alcanzado"
                    theme_text_color: "Custom"
                    text_color: 0, 0, 0, 1
                    font_style: 'Subtitle2'
                    halign: 'center'

        MDBoxLayout:
            orientation: 'horizontal'
            spacing: '10dp'
            # padding: "10dp", 0,"10dp", "10dp"
            size_hint_y: 0.45
            MDCard:
                size_hint: 1, 0.9
                orientation: 'vertical'
                radius: [16, ]

                MDBoxLayout:
                    size_hint: 1, 0.175
                    radius: [16, 16, 0, 0]
                    md_bg_color: 0.114, 0.212, 0.235, 1
                    MDLabel:
                        id: momento_seleccionado
                        text: 'Lineas de tiempo'
                        halign: 'center'
                        theme_text_color: "Custom"
                        text_color: 1, 1, 1, 1
                MDSeparator:
                    height: "2dp"
                    color: 0.95, 0.81, 0.25, 1
                MDBoxLayout:
                    id: timeline_container
                    pos_hint: {'center_x': 0.5, 'center_y': 0.5}
                    radius: [0, 0, 16, 16]
                    MDLabel:
                        id: label_timeline_vacio
                        text: 'Seleccionar temporalidad para visualizar ventas del periodo'
                        halign: 'center'
 

Как вы можете видеть, единственный флажок, который пытается выполнить форматирование тысячи, — это «Год». Таким образом, это будет единственная кнопка, которая приведет к сбою приложения. Обратите внимание, что причина, по которой я использовал переменную делителя, заключается в том, чтобы получить 5 равномерно распределенных тиков. Есть какие-либо предложения о том, как я могу правильно форматировать галочки?

Во второй части вопроса я буду очень признателен за предложения о том, как добавлять всплывающие метки, которые отображаются при нажатии на разные точки графиков. Я нашел такое, но только на пакете Фолиума для карт. Существуют ли какие-либо аналогичные решения для matplotlib?

Заранее большое спасибо.

Ответ №1:

Я верю, что у вас проблема с линией:

         plt.yticks([int(0), format(divider * 1, ','), format(divider * 2, ','), format(divider * 3, ','), format(divider * 4, ',')])
 

yticks() Метод принимает ноль, один или два аргумента. Если вы предоставляете только один аргумент, как в приведенной выше строке, то это должен быть массив чисел, а не строк. Если вы хотите указать фактические метки, то необходимо указать два аргумента — список чисел и другой список строк. Итак, я считаю, что приведенная выше строка должна быть:

 plt.yticks([0, divider * 1, divider * 2, divider * 3, divider * 4], ['0', format(divider * 1, ','), format(divider * 2, ','), format(divider * 3, ','), format(divider * 4, ',')])
 

Комментарии:

1. Большое спасибо за ваш ответ, Джон. Ты был прав. Ваше решение сработало именно так, как ожидалось. У вас есть какие-либо замечания или предложения по моей второй части вопроса? На всплывающих ярлыках? Есть ли модуль, который выполняет эту работу в matplotlib, как в Folium? Еще раз большое спасибо.

2. Мое лучшее предположение для всплывающих меток состояло бы в том, чтобы расширить FigureCanvasKivyAgg и реализовать on_touch_down() метод, который выполняет всплывающие окна. Ничего из того, что я знаю о готовых к использованию пакетах.