Repository: bluenote10/PandasDataFrameGUI
Branch: master
Commit: c4dcd6baa39d
Files: 9
Total size: 37.7 KB
Directory structure:
gitextract_cj2n2164/
├── .gitignore
├── LICENSE
├── README.md
├── demo.py
├── dfgui/
│ ├── __init__.py
│ ├── dfgui.py
│ ├── dnd_list.py
│ └── listmixin.py
└── setup.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
/.idea
/venv*/
*.pyc
*.egg-info
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Pandas DataFrame GUI
A minimalistic GUI for analyzing Pandas DataFrames based on wxPython.
**Update:** I'm currently working on a successor [tabloo](https://github.com/bluenote10/tabloo) which avoids native dependencies and offers a more modern user interface.
## Usage
```python
import dfgui
dfgui.show(df)
```
## Features
- Tabular view of data frame
- Columns are sortable (by clicking column header)
- Columns can be enabled/disabled (left click on 'Columns' tab)
- Columns can be rearranged (right click drag on 'Columns' tab)
- Generic filtering: Write arbitrary Python expression to filter rows. *Warning:* Uses Python's `eval` -- use with care.
- Histogram plots
- Scatter plots
## Demo & Docs
The default view: Nothing fancy, just scrolling and sorting. The value of cell can be copied to clipboard by right clicking on a cell.

The column selection view: Left clicking enables or disables a column in the data frame view. Columns can be dragged with a right click to rearrange them.

The filter view: Allows to write arbitrary Pandas selection expressions. The syntax is: An underscore `_` will be replaced by the corresponding data frame column. That is, setting the combo box to a column named "A" and adding the condition `_ == 1` would result in an expression like `df[df["A"] == 1, :]`. The following example filters the data frame to rows which have the value 669944 in column "UserID" and `datetime.date` value between 2016-01-01 and 2016-03-01.

Histogram view:

Scatter plot view:

## Requirements
Since wxPython is not pip-installable, dfgui does not handle dependencies automatically. You have to make sure the following packages are installed:
- pandas/numpy
- matplotlib
- wx
## Installation Instructions
I haven't submitted dfgui to PyPI (yet), but you can install directly from git (having met all requirements). For instance:
```bash
pip install git+https://github.com/bluenote10/PandasDataFrameGUI
```
or if you prefer a regular git clone:
```bash
git clone git@github.com:bluenote10/PandasDataFrameGUI.git dfgui
cd dfgui
pip install -e .
# and to check if everything works:
./demo.py
```
In fact, dfgui only consists of a single module, so you might as well just download the file [`dfgui/dfgui.py`](dfgui/dfgui.py).
### Anaconda/Windows Instructions
Install wxpython through conda or the Anaconda GUI.
"Open terminal" in the Anaconda GUI environment.
```bash
git clone "https://github.com/bluenote10/PandasDataFrameGUI.git"
cd dfgui
pip install -e .
conda package --pkg-name=dfgui --pkg-version=0.1 # this should create a package file
conda install --offline dfgui-0.1-py27_0.tar.bz2 # this should install into your conda environment
```
Then restart your Jupyter kernel.
================================================
FILE: demo.py
================================================
#!/usr/bin/env python
# -*- encoding: utf-8
from __future__ import absolute_import, division, print_function
"""
If you are getting wx related import errors when running in a virtualenv:
Either make sure that the virtualenv has been created using
`virtualenv --system-site-packages venv` or manually add the wx library
path (e.g. /usr/lib/python2.7/dist-packages/wx-2.8-gtk2-unicode) to the
python path.
"""
import datetime
import pandas as pd
import numpy as np
import dfgui
def create_dummy_data(size):
user_ids = np.random.randint(1, 1000000, 10)
product_ids = np.random.randint(1, 1000000, 100)
def choice(*values):
return np.random.choice(values, size)
random_dates = [
datetime.date(2016, 1, 1) + datetime.timedelta(days=int(delta))
for delta in np.random.randint(1, 50, size)
]
return pd.DataFrame.from_items([
("Date", random_dates),
("UserID", choice(*user_ids)),
("ProductID", choice(*product_ids)),
("IntColumn", choice(1, 2, 3)),
("FloatColumn", choice(np.nan, 1.0, 2.0, 3.0)),
("StringColumn", choice("A", "B", "C")),
("Gaussian 1", np.random.normal(0, 1, size)),
("Gaussian 2", np.random.normal(0, 1, size)),
("Uniform", np.random.uniform(0, 1, size)),
("Binomial", np.random.binomial(20, 0.1, size)),
("Poisson", np.random.poisson(1.0, size)),
])
df = create_dummy_data(1000)
dfgui.show(df)
================================================
FILE: dfgui/__init__.py
================================================
from __future__ import absolute_import
from dfgui.dfgui import show
__all__ = [
"show"
]
================================================
FILE: dfgui/dfgui.py
================================================
#!/usr/bin/env python
# -*- encoding: utf-8
from __future__ import absolute_import, division, print_function
try:
import wx
except ImportError:
import sys
sys.path += [
"/usr/lib/python2.7/dist-packages/wx-2.8-gtk2-unicode",
"/usr/lib/python2.7/dist-packages"
]
import wx
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wx import NavigationToolbar2Wx
from matplotlib.figure import Figure
from bisect import bisect
import numpy as np
import pandas as pd
# unused import required to allow 'eval' of date filters
import datetime
from datetime import date
# try to get nicer plotting styles
try:
import seaborn
seaborn.set()
except ImportError:
try:
from matplotlib import pyplot as plt
plt.style.use('ggplot')
except AttributeError:
pass
class ListCtrlDataFrame(wx.ListCtrl):
# TODO: we could do something more sophisticated to come
# TODO: up with a reasonable column width...
DEFAULT_COLUMN_WIDTH = 100
TMP_SELECTION_COLUMN = 'tmp_selection_column'
def __init__(self, parent, df, status_bar_callback):
wx.ListCtrl.__init__(
self, parent, -1,
style=wx.LC_REPORT | wx.LC_VIRTUAL | wx.LC_HRULES | wx.LC_VRULES | wx.LB_MULTIPLE
)
self.status_bar_callback = status_bar_callback
self.df_orig = df
self.original_columns = self.df_orig.columns[:]
if isinstance(self.original_columns,(pd.RangeIndex,pd.Int64Index)):
# RangeIndex is not supported by self._update_columns
self.original_columns = pd.Index([str(i) for i in self.original_columns])
self.current_columns = self.df_orig.columns[:]
self.sort_by_column = None
self._reset_mask()
# prepare attribute for alternating colors of rows
self.attr_light_blue = wx.ListItemAttr()
self.attr_light_blue.SetBackgroundColour("#D6EBFF")
self.Bind(wx.EVT_LIST_COL_CLICK, self._on_col_click)
self.Bind(wx.EVT_RIGHT_DOWN, self._on_right_click)
self.df = pd.DataFrame({}) # init empty to force initial update
self._update_rows()
self._update_columns(self.original_columns)
def _reset_mask(self):
#self.mask = [True] * self.df_orig.shape[0]
self.mask = pd.Series([True] * self.df_orig.shape[0], index=self.df_orig.index)
def _update_columns(self, columns):
self.ClearAll()
for i, col in enumerate(columns):
self.InsertColumn(i, col)
self.SetColumnWidth(i, self.DEFAULT_COLUMN_WIDTH)
# Note that we have to reset the count as well because ClearAll()
# not only deletes columns but also the count...
self.SetItemCount(len(self.df))
def set_columns(self, columns_to_use):
"""
External interface to set the column projections.
"""
self.current_columns = columns_to_use
self._update_rows()
self._update_columns(columns_to_use)
def _update_rows(self):
old_len = len(self.df)
self.df = self.df_orig.loc[self.mask.values, self.current_columns]
new_len = len(self.df)
if old_len != new_len:
self.SetItemCount(new_len)
self.status_bar_callback(0, "Number of rows: {}".format(new_len))
def apply_filter(self, conditions):
"""
External interface to set a filter.
"""
old_mask = self.mask.copy()
if len(conditions) == 0:
self._reset_mask()
else:
self._reset_mask() # set all to True for destructive conjunction
no_error = True
for column, condition in conditions:
if condition.strip() == '':
continue
condition = condition.replace("_", "self.df_orig['{}']".format(column))
print("Evaluating condition:", condition)
try:
tmp_mask = eval(condition)
if isinstance(tmp_mask, pd.Series) and tmp_mask.dtype == np.bool:
self.mask &= tmp_mask
except Exception as e:
print("Failed with:", e)
no_error = False
self.status_bar_callback(
1,
"Evaluating '{}' failed with: {}".format(condition, e)
)
if no_error:
self.status_bar_callback(1, "")
has_changed = any(old_mask != self.mask)
if has_changed:
self._update_rows()
return len(self.df), has_changed
def get_selected_items(self):
"""
Gets the selected items for the list control.
Selection is returned as a list of selected indices,
low to high.
"""
selection = []
current = -1 # start at -1 to get the first selected item
while True:
next = self.GetNextItem(current, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED)
if next == -1:
return selection
else:
selection.append(next)
current = next
def get_filtered_df(self):
return self.df_orig.loc[self.mask, :]
def _on_col_click(self, event):
"""
Sort data frame by selected column.
"""
# get currently selected items
selected = self.get_selected_items()
# append a temporary column to store the currently selected items
self.df[self.TMP_SELECTION_COLUMN] = False
self.df.iloc[selected, -1] = True
# get column name to use for sorting
col = event.GetColumn()
# determine if ascending or descending
if self.sort_by_column is None or self.sort_by_column[0] != col:
ascending = True
else:
ascending = not self.sort_by_column[1]
# store sort column and sort direction
self.sort_by_column = (col, ascending)
try:
# pandas 0.17
self.df.sort_values(self.df.columns[col], inplace=True, ascending=ascending)
except AttributeError:
# pandas 0.16 compatibility
self.df.sort(self.df.columns[col], inplace=True, ascending=ascending)
# deselect all previously selected
for i in selected:
self.Select(i, on=False)
# determine indices of selection after sorting
selected_bool = self.df.iloc[:, -1] == True
selected = self.df.reset_index().index[selected_bool]
# select corresponding rows
for i in selected:
self.Select(i, on=True)
# delete temporary column
del self.df[self.TMP_SELECTION_COLUMN]
def _on_right_click(self, event):
"""
Copies a cell into clipboard on right click. Unfortunately,
determining the clicked column is not straightforward. This
appraoch is inspired by the TextEditMixin in:
/usr/lib/python2.7/dist-packages/wx-2.8-gtk2-unicode/wx/lib/mixins/listctrl.py
More references:
- http://wxpython-users.1045709.n5.nabble.com/Getting-row-col-of-selected-cell-in-ListCtrl-td2360831.html
- https://groups.google.com/forum/#!topic/wxpython-users/7BNl9TA5Y5U
- https://groups.google.com/forum/#!topic/wxpython-users/wyayJIARG8c
"""
if self.HitTest(event.GetPosition()) != wx.NOT_FOUND:
x, y = event.GetPosition()
row, flags = self.HitTest((x, y))
col_locs = [0]
loc = 0
for n in range(self.GetColumnCount()):
loc = loc + self.GetColumnWidth(n)
col_locs.append(loc)
scroll_pos = self.GetScrollPos(wx.HORIZONTAL)
# this is crucial step to get the scroll pixel units
unit_x, unit_y = self.GetMainWindow().GetScrollPixelsPerUnit()
col = bisect(col_locs, x + scroll_pos * unit_x) - 1
value = self.df.iloc[row, col]
# print(row, col, scroll_pos, value)
clipdata = wx.TextDataObject()
clipdata.SetText(str(value))
wx.TheClipboard.Open()
wx.TheClipboard.SetData(clipdata)
wx.TheClipboard.Close()
def OnGetItemText(self, item, col):
"""
Implements the item getter for a "virtual" ListCtrl.
"""
value = self.df.iloc[item, col]
# print("retrieving %d %d %s" % (item, col, value))
return str(value)
def OnGetItemAttr(self, item):
"""
Implements the attribute getter for a "virtual" ListCtrl.
"""
if item % 2 == 0:
return self.attr_light_blue
else:
return None
class DataframePanel(wx.Panel):
"""
Panel providing the main data frame table view.
"""
def __init__(self, parent, df, status_bar_callback):
wx.Panel.__init__(self, parent)
self.df_list_ctrl = ListCtrlDataFrame(self, df, status_bar_callback)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.df_list_ctrl, 1, wx.ALL | wx.EXPAND | wx.GROW, 5)
self.SetSizer(sizer)
self.Show()
class ListBoxDraggable(wx.ListBox):
"""
Helper class to provide ListBox with extended behavior.
"""
def __init__(self, parent, size, data, *args, **kwargs):
wx.ListBox.__init__(self, parent, size, **kwargs)
if isinstance(data,(pd.RangeIndex,pd.Int64Index)):
# RangeIndex is not supported by self._update_columns
data = pd.Index([str(i) for i in data])
self.data = data
self.InsertItems(self.data, 0)
self.Bind(wx.EVT_LISTBOX, self.on_selection_changed)
self.Bind(wx.EVT_LEFT_DOWN, self.on_left_down)
self.Bind(wx.EVT_RIGHT_DOWN, self.on_right_down)
self.Bind(wx.EVT_RIGHT_UP, self.on_right_up)
self.Bind(wx.EVT_MOTION, self.on_move)
self.index_iter = range(len(self.data))
self.selected_items = [True] * len(self.data)
self.index_mapping = list(range(len(self.data)))
self.drag_start_index = None
self.update_selection()
self.SetFocus()
def on_left_down(self, event):
if self.HitTest(event.GetPosition()) != wx.NOT_FOUND:
index = self.HitTest(event.GetPosition())
self.selected_items[index] = not self.selected_items[index]
# doesn't really work to update selection direclty (focus issues)
# instead we wait for the EVT_LISTBOX event and fix the selection
# there...
# self.update_selection()
# TODO: we could probably use wx.CallAfter
event.Skip()
def update_selection(self):
# self.SetFocus()
# print(self.selected_items)
for i in self.index_iter:
if self.IsSelected(i) and not self.selected_items[i]:
#print("Deselecting", i)
self.Deselect(i)
elif not self.IsSelected(i) and self.selected_items[i]:
#print("Selecting", i)
self.Select(i)
def on_selection_changed(self, evt):
self.update_selection()
evt.Skip()
def on_right_down(self, event):
if self.HitTest(event.GetPosition()) != wx.NOT_FOUND:
index = self.HitTest(event.GetPosition())
self.drag_start_index = index
def on_right_up(self, event):
self.drag_start_index = None
event.Skip()
def on_move(self, event):
if self.drag_start_index is not None:
if self.HitTest(event.GetPosition()) != wx.NOT_FOUND:
index = self.HitTest(event.GetPosition())
if self.drag_start_index != index:
self.swap(self.drag_start_index, index)
self.drag_start_index = index
def swap(self, i, j):
self.index_mapping[i], self.index_mapping[j] = self.index_mapping[j], self.index_mapping[i]
self.SetString(i, self.data[self.index_mapping[i]])
self.SetString(j, self.data[self.index_mapping[j]])
self.selected_items[i], self.selected_items[j] = self.selected_items[j], self.selected_items[i]
# self.update_selection()
# print("Updated mapping:", self.index_mapping)
new_event = wx.PyCommandEvent(wx.EVT_LISTBOX.typeId, self.GetId())
self.GetEventHandler().ProcessEvent(new_event)
def get_selected_data(self):
selected = []
for i, col in enumerate(self.data):
if self.IsSelected(i):
index = self.index_mapping[i]
value = self.data[index]
selected.append(value)
# print("Selected data:", selected)
return selected
class ColumnSelectionPanel(wx.Panel):
"""
Panel for selecting and re-arranging columns.
"""
def __init__(self, parent, columns, df_list_ctrl):
wx.Panel.__init__(self, parent)
self.columns = columns
self.df_list_ctrl = df_list_ctrl
self.list_box = ListBoxDraggable(self, -1, columns, style=wx.LB_EXTENDED)
self.Bind(wx.EVT_LISTBOX, self.update_selected_columns)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.list_box, 1, wx.ALL | wx.EXPAND | wx.GROW, 5)
self.SetSizer(sizer)
self.list_box.SetFocus()
def update_selected_columns(self, evt):
selected = self.list_box.get_selected_data()
self.df_list_ctrl.set_columns(selected)
class FilterPanel(wx.Panel):
"""
Panel for defining filter expressions.
"""
def __init__(self, parent, columns, df_list_ctrl, change_callback):
wx.Panel.__init__(self, parent)
columns_with_neutral_selection = [''] + list(columns)
self.columns = columns
self.df_list_ctrl = df_list_ctrl
self.change_callback = change_callback
self.num_filters = 10
self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.combo_boxes = []
self.text_controls = []
for i in range(self.num_filters):
combo_box = wx.ComboBox(self, choices=columns_with_neutral_selection, style=wx.CB_READONLY)
text_ctrl = wx.TextCtrl(self, wx.ID_ANY, '')
self.Bind(wx.EVT_COMBOBOX, self.on_combo_box_select)
self.Bind(wx.EVT_TEXT, self.on_text_change)
row_sizer = wx.BoxSizer(wx.HORIZONTAL)
row_sizer.Add(combo_box, 0, wx.ALL, 5)
row_sizer.Add(text_ctrl, 1, wx.ALL | wx.EXPAND | wx.ALIGN_RIGHT, 5)
self.combo_boxes.append(combo_box)
self.text_controls.append(text_ctrl)
self.main_sizer.Add(row_sizer, 0, wx.EXPAND)
self.SetSizer(self.main_sizer)
def on_combo_box_select(self, event):
self.update_conditions()
def on_text_change(self, event):
self.update_conditions()
def update_conditions(self):
# print("Updating conditions")
conditions = []
for i in range(self.num_filters):
column_index = self.combo_boxes[i].GetSelection()
condition = self.text_controls[i].GetValue()
if column_index != wx.NOT_FOUND and column_index != 0:
# since we have added a dummy column for "deselect", we have to subtract one
column = self.columns[column_index - 1]
conditions += [(column, condition)]
num_matching, has_changed = self.df_list_ctrl.apply_filter(conditions)
if has_changed:
self.change_callback()
# print("Num matching:", num_matching)
class HistogramPlot(wx.Panel):
"""
Panel providing a histogram plot.
"""
def __init__(self, parent, columns, df_list_ctrl):
wx.Panel.__init__(self, parent)
columns_with_neutral_selection = [''] + list(columns)
self.columns = columns
self.df_list_ctrl = df_list_ctrl
self.figure = Figure(facecolor="white", figsize=(1, 1))
self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, -1, self.figure)
chart_toolbar = NavigationToolbar2Wx(self.canvas)
self.combo_box1 = wx.ComboBox(self, choices=columns_with_neutral_selection, style=wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.on_combo_box_select)
row_sizer = wx.BoxSizer(wx.HORIZONTAL)
row_sizer.Add(self.combo_box1, 0, wx.ALL | wx.ALIGN_CENTER, 5)
row_sizer.Add(chart_toolbar, 0, wx.ALL, 5)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas, 1, flag=wx.EXPAND, border=5)
sizer.Add(row_sizer)
self.SetSizer(sizer)
def on_combo_box_select(self, event):
self.redraw()
def redraw(self):
column_index1 = self.combo_box1.GetSelection()
if column_index1 != wx.NOT_FOUND and column_index1 != 0:
# subtract one to remove the neutral selection index
column_index1 -= 1
df = self.df_list_ctrl.get_filtered_df()
if len(df) > 0:
self.axes.clear()
column = df.iloc[:, column_index1]
is_string_col = column.dtype == np.object and isinstance(column.values[0], str)
if is_string_col:
value_counts = column.value_counts().sort_index()
value_counts.plot(kind='bar', ax=self.axes)
else:
self.axes.hist(column.values, bins=100)
self.canvas.draw()
class ScatterPlot(wx.Panel):
"""
Panel providing a scatter plot.
"""
def __init__(self, parent, columns, df_list_ctrl):
wx.Panel.__init__(self, parent)
columns_with_neutral_selection = [''] + list(columns)
self.columns = columns
self.df_list_ctrl = df_list_ctrl
self.figure = Figure(facecolor="white", figsize=(1, 1))
self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, -1, self.figure)
chart_toolbar = NavigationToolbar2Wx(self.canvas)
self.combo_box1 = wx.ComboBox(self, choices=columns_with_neutral_selection, style=wx.CB_READONLY)
self.combo_box2 = wx.ComboBox(self, choices=columns_with_neutral_selection, style=wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.on_combo_box_select)
row_sizer = wx.BoxSizer(wx.HORIZONTAL)
row_sizer.Add(self.combo_box1, 0, wx.ALL | wx.ALIGN_CENTER, 5)
row_sizer.Add(self.combo_box2, 0, wx.ALL | wx.ALIGN_CENTER, 5)
row_sizer.Add(chart_toolbar, 0, wx.ALL, 5)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.canvas, 1, flag=wx.EXPAND, border=5)
sizer.Add(row_sizer)
self.SetSizer(sizer)
def on_combo_box_select(self, event):
self.redraw()
def redraw(self):
column_index1 = self.combo_box1.GetSelection()
column_index2 = self.combo_box2.GetSelection()
if column_index1 != wx.NOT_FOUND and column_index1 != 0 and \
column_index2 != wx.NOT_FOUND and column_index2 != 0:
# subtract one to remove the neutral selection index
column_index1 -= 1
column_index2 -= 1
df = self.df_list_ctrl.get_filtered_df()
# It looks like using pandas dataframe.plot causes something weird to
# crash in wx internally. Therefore we use plain axes.plot functionality.
# column_name1 = self.columns[column_index1]
# column_name2 = self.columns[column_index2]
# df.plot(kind='scatter', x=column_name1, y=column_name2)
if len(df) > 0:
self.axes.clear()
self.axes.plot(df.iloc[:, column_index1].values, df.iloc[:, column_index2].values, 'o', clip_on=False)
self.canvas.draw()
class MainFrame(wx.Frame):
"""
The main GUI window.
"""
def __init__(self, df):
wx.Frame.__init__(self, None, -1, "Pandas DataFrame GUI")
# Here we create a panel and a notebook on the panel
p = wx.Panel(self)
nb = wx.Notebook(p)
self.nb = nb
columns = df.columns[:]
if isinstance(columns,(pd.RangeIndex,pd.Int64Index)):
# RangeIndex is not supported
columns = pd.Index([str(i) for i in columns])
self.CreateStatusBar(2, style=0)
self.SetStatusWidths([200, -1])
# create the page windows as children of the notebook
self.page1 = DataframePanel(nb, df, self.status_bar_callback)
self.page2 = ColumnSelectionPanel(nb, columns, self.page1.df_list_ctrl)
self.page3 = FilterPanel(nb, columns, self.page1.df_list_ctrl, self.selection_change_callback)
self.page4 = HistogramPlot(nb, columns, self.page1.df_list_ctrl)
self.page5 = ScatterPlot(nb, columns, self.page1.df_list_ctrl)
# add the pages to the notebook with the label to show on the tab
nb.AddPage(self.page1, "Data Frame")
nb.AddPage(self.page2, "Columns")
nb.AddPage(self.page3, "Filters")
nb.AddPage(self.page4, "Histogram")
nb.AddPage(self.page5, "Scatter Plot")
nb.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_tab_change)
# finally, put the notebook in a sizer for the panel to manage
# the layout
sizer = wx.BoxSizer()
sizer.Add(nb, 1, wx.EXPAND)
p.SetSizer(sizer)
self.SetSize((800, 600))
self.Center()
def on_tab_change(self, event):
self.page2.list_box.SetFocus()
page_to_select = event.GetSelection()
wx.CallAfter(self.fix_focus, page_to_select)
event.Skip(True)
def fix_focus(self, page_to_select):
page = self.nb.GetPage(page_to_select)
page.SetFocus()
if isinstance(page, DataframePanel):
self.page1.df_list_ctrl.SetFocus()
elif isinstance(page, ColumnSelectionPanel):
self.page2.list_box.SetFocus()
def status_bar_callback(self, i, new_text):
self.SetStatusText(new_text, i)
def selection_change_callback(self):
self.page4.redraw()
self.page5.redraw()
def show(df):
"""
The main function to start the data frame GUI.
"""
app = wx.App(False)
frame = MainFrame(df)
frame.Show()
app.MainLoop()
================================================
FILE: dfgui/dnd_list.py
================================================
""" DnD demo with listctrl. """
import sys
sys.path.append("/usr/lib/python2.7/dist-packages/wx-2.8-gtk2-unicode")
import wx
class DragList(wx.ListCtrl):
def __init__(self, *arg, **kw):
if 'style' in kw and (kw['style']&wx.LC_LIST or kw['style']&wx.LC_REPORT):
kw['style'] |= wx.LC_SINGLE_SEL
else:
kw['style'] = wx.LC_SINGLE_SEL|wx.LC_LIST
wx.ListCtrl.__init__(self, *arg, **kw)
self.Bind(wx.EVT_LIST_BEGIN_DRAG, self._startDrag)
dt = ListDrop(self._insert)
self.SetDropTarget(dt)
def _startDrag(self, e):
""" Put together a data object for drag-and-drop _from_ this list. """
# Create the data object: Just use plain text.
data = wx.PyTextDataObject()
idx = e.GetIndex()
text = self.GetItem(idx).GetText()
data.SetText(text)
# Create drop source and begin drag-and-drop.
dropSource = wx.DropSource(self)
dropSource.SetData(data)
res = dropSource.DoDragDrop(flags=wx.Drag_DefaultMove)
# If move, we want to remove the item from this list.
if res == wx.DragMove:
# It's possible we are dragging/dropping from this list to this list. In which case, the
# index we are removing may have changed...
# Find correct position.
pos = self.FindItem(idx, text)
self.DeleteItem(pos)
def _insert(self, x, y, text):
""" Insert text at given x, y coordinates --- used with drag-and-drop. """
# Clean text.
import string
text = filter(lambda x: x in (string.letters + string.digits + string.punctuation + ' '), text)
# Find insertion point.
index, flags = self.HitTest((x, y))
if index == wx.NOT_FOUND:
if flags & wx.LIST_HITTEST_NOWHERE:
index = self.GetItemCount()
else:
return
# Get bounding rectangle for the item the user is dropping over.
rect = self.GetItemRect(index)
# If the user is dropping into the lower half of the rect, we want to insert _after_ this item.
if y > rect.y + rect.height/2:
index += 1
self.InsertStringItem(index, text)
class ListDrop(wx.PyDropTarget):
""" Drop target for simple lists. """
def __init__(self, setFn):
""" Arguments:
- setFn: Function to call on drop.
"""
wx.PyDropTarget.__init__(self)
self.setFn = setFn
# specify the type of data we will accept
self.data = wx.PyTextDataObject()
self.SetDataObject(self.data)
# Called when OnDrop returns True. We need to get the data and
# do something with it.
def OnData(self, x, y, d):
# copy the data from the drag source to our data object
if self.GetData():
self.setFn(x, y, self.data.GetText())
# what is returned signals the source what to do
# with the original data (move, copy, etc.) In this
# case we just return the suggested value given to us.
return d
if __name__ == '__main__':
items = ['Foo', 'Bar', 'Baz', 'Zif', 'Zaf', 'Zof']
class MyApp(wx.App):
def OnInit(self):
self.frame = wx.Frame(None, title='Main Frame')
self.frame.Show(True)
self.SetTopWindow(self.frame)
return True
app = MyApp(redirect=False)
dl1 = DragList(app.frame)
dl2 = DragList(app.frame)
sizer = wx.BoxSizer()
app.frame.SetSizer(sizer)
sizer.Add(dl1, proportion=1, flag=wx.EXPAND)
sizer.Add(dl2, proportion=1, flag=wx.EXPAND)
for item in items:
dl1.InsertStringItem(99, item)
dl2.InsertStringItem(99, item)
app.frame.Layout()
app.MainLoop()
================================================
FILE: dfgui/listmixin.py
================================================
#----------------------------------------------------------------------------
# Name: wxPython.lib.mixins.listctrl
# Purpose: Helpful mix-in classes for wxListCtrl
#
# Author: Robin Dunn
#
# Created: 15-May-2001
# RCS-ID: $Id: listctrl.py 63322 2010-01-30 00:59:55Z RD $
# Copyright: (c) 2001 by Total Control Software
# Licence: wxWindows license
#----------------------------------------------------------------------------
# 12/14/2003 - Jeff Grimmett (grimmtooth@softhome.net)
#
# o 2.5 compatability update.
# o ListCtrlSelectionManagerMix untested.
#
# 12/21/2003 - Jeff Grimmett (grimmtooth@softhome.net)
#
# o wxColumnSorterMixin -> ColumnSorterMixin
# o wxListCtrlAutoWidthMixin -> ListCtrlAutoWidthMixin
# ...
# 13/10/2004 - Pim Van Heuven (pim@think-wize.com)
# o wxTextEditMixin: Support Horizontal scrolling when TAB is pressed on long
# ListCtrls, support for WXK_DOWN, WXK_UP, performance improvements on
# very long ListCtrls, Support for virtual ListCtrls
#
# 15-Oct-2004 - Robin Dunn
# o wxTextEditMixin: Added Shift-TAB support
#
# 2008-11-19 - raf <raf@raf.org>
# o ColumnSorterMixin: Added GetSortState()
#
import locale
import wx
#----------------------------------------------------------------------------
class ColumnSorterMixin:
"""
A mixin class that handles sorting of a wx.ListCtrl in REPORT mode when
the column header is clicked on.
There are a few requirments needed in order for this to work genericly:
1. The combined class must have a GetListCtrl method that
returns the wx.ListCtrl to be sorted, and the list control
must exist at the time the wx.ColumnSorterMixin.__init__
method is called because it uses GetListCtrl.
2. Items in the list control must have a unique data value set
with list.SetItemData.
3. The combined class must have an attribute named itemDataMap
that is a dictionary mapping the data values to a sequence of
objects representing the values in each column. These values
are compared in the column sorter to determine sort order.
Interesting methods to override are GetColumnSorter,
GetSecondarySortValues, and GetSortImages. See below for details.
"""
def __init__(self, numColumns, preSortCallback = None):
self.SetColumnCount(numColumns)
self.preSortCallback = preSortCallback
list = self.GetListCtrl()
if not list:
raise ValueError, "No wx.ListCtrl available"
list.Bind(wx.EVT_LIST_COL_CLICK, self.__OnColClick, list)
def SetColumnCount(self, newNumColumns):
self._colSortFlag = [0] * newNumColumns
self._col = -1
def SortListItems(self, col=-1, ascending=1):
"""Sort the list on demand. Can also be used to set the sort column and order."""
oldCol = self._col
if col != -1:
self._col = col
self._colSortFlag[col] = ascending
self.GetListCtrl().SortItems(self.GetColumnSorter())
self.__updateImages(oldCol)
def GetColumnWidths(self):
"""
Returns a list of column widths. Can be used to help restore the current
view later.
"""
list = self.GetListCtrl()
rv = []
for x in range(len(self._colSortFlag)):
rv.append(list.GetColumnWidth(x))
return rv
def GetSortImages(self):
"""
Returns a tuple of image list indexesthe indexes in the image list for an image to be put on the column
header when sorting in descending order.
"""
return (-1, -1) # (decending, ascending) image IDs
def GetColumnSorter(self):
"""Returns a callable object to be used for comparing column values when sorting."""
return self.__ColumnSorter
def GetSecondarySortValues(self, col, key1, key2):
"""Returns a tuple of 2 values to use for secondary sort values when the
items in the selected column match equal. The default just returns the
item data values."""
return (key1, key2)
def __OnColClick(self, evt):
if self.preSortCallback is not None:
self.preSortCallback()
oldCol = self._col
self._col = col = evt.GetColumn()
self._colSortFlag[col] = int(not self._colSortFlag[col])
self.GetListCtrl().SortItems(self.GetColumnSorter())
if wx.Platform != "__WXMAC__" or wx.SystemOptions.GetOptionInt("mac.listctrl.always_use_generic") == 1:
self.__updateImages(oldCol)
evt.Skip()
self.OnSortOrderChanged()
def OnSortOrderChanged(self):
"""
Callback called after sort order has changed (whenever user
clicked column header).
"""
pass
def GetSortState(self):
"""
Return a tuple containing the index of the column that was last sorted
and the sort direction of that column.
Usage:
col, ascending = self.GetSortState()
# Make changes to list items... then resort
self.SortListItems(col, ascending)
"""
return (self._col, self._colSortFlag[self._col])
def __ColumnSorter(self, key1, key2):
col = self._col
ascending = self._colSortFlag[col]
item1 = self.itemDataMap[key1][col]
item2 = self.itemDataMap[key2][col]
#--- Internationalization of string sorting with locale module
if type(item1) == unicode and type(item2) == unicode:
cmpVal = locale.strcoll(item1, item2)
elif type(item1) == str or type(item2) == str:
cmpVal = locale.strcoll(str(item1), str(item2))
else:
cmpVal = cmp(item1, item2)
#---
# If the items are equal then pick something else to make the sort value unique
if cmpVal == 0:
cmpVal = apply(cmp, self.GetSecondarySortValues(col, key1, key2))
if ascending:
return cmpVal
else:
return -cmpVal
def __updateImages(self, oldCol):
sortImages = self.GetSortImages()
if self._col != -1 and sortImages[0] != -1:
img = sortImages[self._colSortFlag[self._col]]
list = self.GetListCtrl()
if oldCol != -1:
list.ClearColumnImage(oldCol)
list.SetColumnImage(self._col, img)
================================================
FILE: setup.py
================================================
from setuptools import setup
setup(
name='dfgui',
version='0.1',
description='Pandas DataFrame GUI',
url='http://github.com/bluenote10/PandasDataFrameGUI',
author='Fabian Keller',
author_email='fabian.keller@blue-yonder.com',
license='MIT',
packages=['dfgui'],
zip_safe=False
)
gitextract_cj2n2164/ ├── .gitignore ├── LICENSE ├── README.md ├── demo.py ├── dfgui/ │ ├── __init__.py │ ├── dfgui.py │ ├── dnd_list.py │ └── listmixin.py └── setup.py
SYMBOL INDEX (71 symbols across 4 files)
FILE: demo.py
function create_dummy_data (line 20) | def create_dummy_data(size):
FILE: dfgui/dfgui.py
class ListCtrlDataFrame (line 42) | class ListCtrlDataFrame(wx.ListCtrl):
method __init__ (line 49) | def __init__(self, parent, df, status_bar_callback):
method _reset_mask (line 78) | def _reset_mask(self):
method _update_columns (line 82) | def _update_columns(self, columns):
method set_columns (line 91) | def set_columns(self, columns_to_use):
method _update_rows (line 99) | def _update_rows(self):
method apply_filter (line 107) | def apply_filter(self, conditions):
method get_selected_items (line 146) | def get_selected_items(self):
method get_filtered_df (line 162) | def get_filtered_df(self):
method _on_col_click (line 165) | def _on_col_click(self, event):
method _on_right_click (line 210) | def _on_right_click(self, event):
method OnGetItemText (line 246) | def OnGetItemText(self, item, col):
method OnGetItemAttr (line 254) | def OnGetItemAttr(self, item):
class DataframePanel (line 264) | class DataframePanel(wx.Panel):
method __init__ (line 268) | def __init__(self, parent, df, status_bar_callback):
class ListBoxDraggable (line 279) | class ListBoxDraggable(wx.ListBox):
method __init__ (line 283) | def __init__(self, parent, size, data, *args, **kwargs):
method on_left_down (line 312) | def on_left_down(self, event):
method update_selection (line 323) | def update_selection(self):
method on_selection_changed (line 334) | def on_selection_changed(self, evt):
method on_right_down (line 338) | def on_right_down(self, event):
method on_right_up (line 343) | def on_right_up(self, event):
method on_move (line 347) | def on_move(self, event):
method swap (line 355) | def swap(self, i, j):
method get_selected_data (line 365) | def get_selected_data(self):
class ColumnSelectionPanel (line 376) | class ColumnSelectionPanel(wx.Panel):
method __init__ (line 380) | def __init__(self, parent, columns, df_list_ctrl):
method update_selected_columns (line 394) | def update_selected_columns(self, evt):
class FilterPanel (line 399) | class FilterPanel(wx.Panel):
method __init__ (line 403) | def __init__(self, parent, columns, df_list_ctrl, change_callback):
method on_combo_box_select (line 435) | def on_combo_box_select(self, event):
method on_text_change (line 438) | def on_text_change(self, event):
method update_conditions (line 441) | def update_conditions(self):
class HistogramPlot (line 457) | class HistogramPlot(wx.Panel):
method __init__ (line 461) | def __init__(self, parent, columns, df_list_ctrl):
method on_combo_box_select (line 487) | def on_combo_box_select(self, event):
method redraw (line 490) | def redraw(self):
class ScatterPlot (line 512) | class ScatterPlot(wx.Panel):
method __init__ (line 516) | def __init__(self, parent, columns, df_list_ctrl):
method on_combo_box_select (line 544) | def on_combo_box_select(self, event):
method redraw (line 547) | def redraw(self):
class MainFrame (line 570) | class MainFrame(wx.Frame):
method __init__ (line 574) | def __init__(self, df):
method on_tab_change (line 614) | def on_tab_change(self, event):
method fix_focus (line 620) | def fix_focus(self, page_to_select):
method status_bar_callback (line 628) | def status_bar_callback(self, i, new_text):
method selection_change_callback (line 631) | def selection_change_callback(self):
function show (line 636) | def show(df):
FILE: dfgui/dnd_list.py
class DragList (line 7) | class DragList(wx.ListCtrl):
method __init__ (line 8) | def __init__(self, *arg, **kw):
method _startDrag (line 21) | def _startDrag(self, e):
method _insert (line 44) | def _insert(self, x, y, text):
class ListDrop (line 69) | class ListDrop(wx.PyDropTarget):
method __init__ (line 72) | def __init__(self, setFn):
method OnData (line 86) | def OnData(self, x, y, d):
class MyApp (line 99) | class MyApp(wx.App):
method OnInit (line 100) | def OnInit(self):
FILE: dfgui/listmixin.py
class ColumnSorterMixin (line 39) | class ColumnSorterMixin:
method __init__ (line 63) | def __init__(self, numColumns, preSortCallback = None):
method SetColumnCount (line 72) | def SetColumnCount(self, newNumColumns):
method SortListItems (line 77) | def SortListItems(self, col=-1, ascending=1):
method GetColumnWidths (line 87) | def GetColumnWidths(self):
method GetSortImages (line 99) | def GetSortImages(self):
method GetColumnSorter (line 107) | def GetColumnSorter(self):
method GetSecondarySortValues (line 112) | def GetSecondarySortValues(self, col, key1, key2):
method __OnColClick (line 119) | def __OnColClick(self, evt):
method OnSortOrderChanged (line 132) | def OnSortOrderChanged(self):
method GetSortState (line 140) | def GetSortState(self):
method __ColumnSorter (line 152) | def __ColumnSorter(self, key1, key2):
method __updateImages (line 177) | def __updateImages(self, oldCol):
Condensed preview — 9 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (40K chars).
[
{
"path": ".gitignore",
"chars": 33,
"preview": "/.idea\n/venv*/\n\n*.pyc\n*.egg-info\n"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 \n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
},
{
"path": "README.md",
"chars": 2989,
"preview": "# Pandas DataFrame GUI\n\nA minimalistic GUI for analyzing Pandas DataFrames based on wxPython.\n\n**Update:** I'm currently"
},
{
"path": "demo.py",
"chars": 1457,
"preview": "#!/usr/bin/env python\n# -*- encoding: utf-8\n\nfrom __future__ import absolute_import, division, print_function\n\n\"\"\"\nIf yo"
},
{
"path": "dfgui/__init__.py",
"chars": 95,
"preview": "from __future__ import absolute_import\n\nfrom dfgui.dfgui import show\n\n__all__ = [\n \"show\"\n]\n"
},
{
"path": "dfgui/dfgui.py",
"chars": 22398,
"preview": "#!/usr/bin/env python\n# -*- encoding: utf-8\n\nfrom __future__ import absolute_import, division, print_function\n\ntry:\n "
},
{
"path": "dfgui/dnd_list.py",
"chars": 3794,
"preview": "\"\"\" DnD demo with listctrl. \"\"\"\nimport sys\nsys.path.append(\"/usr/lib/python2.7/dist-packages/wx-2.8-gtk2-unicode\")\n\nimpo"
},
{
"path": "dfgui/listmixin.py",
"chars": 6416,
"preview": "#----------------------------------------------------------------------------\n# Name: wxPython.lib.mixins.listctr"
},
{
"path": "setup.py",
"chars": 315,
"preview": "from setuptools import setup\n\nsetup(\n name='dfgui',\n version='0.1',\n description='Pandas DataFrame GUI',\n ur"
}
]
About this extraction
This page contains the full source code of the bluenote10/PandasDataFrameGUI GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 9 files (37.7 KB), approximately 9.3k tokens, and a symbol index with 71 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.