def __lt__(self, other): item0 = self.text(self.sortColumn) item1 = other.text(self.sortColumn) if isFloat(item0) and isFloat(item1): return float(item0) < float(item1) elif checkMonthYearFloat(item0) and checkMonthYearFloat(item1): value0 = monthYearToFloat(item0) value1 = monthYearToFloat(item1) return value0 < value1 elif checkHourMinSecFloat(item0) and checkHourMinSecFloat(item1): value0 = hourMinSecToFloat(item0) value1 = hourMinSecToFloat(item1) return value0 < value1 else: return item0 < item1
def _makeAvgSpeedColumn(self, df): ## Avg. speed was not always included in csv file ## If user does not have this column, create it if 'Avg. speed (km/h)' not in df.columns: times = np.array([hourMinSecToFloat(t) for t in df['Time']]) df['Avg. speed (km/h)'] = df['Distance (km)'] / times return df
def update(self, values): """ Update items in the underlying DataFrame. `values` should be a dict; the keys should be indices and values should be dicts of column:value. If the value currently at the given column and index is different from that supplied in the dictionary, it will be updated. If changes are made, `dataChanged` is emitted. Example `values` structure: {10: {'Distance (km)':25, 'Calories':375}} """ changed = [] for index, dct in values.items(): for col, value in dct.items(): if self.df.at[index, col] != value: self.df.at[index, col] = value changed.append(index) if changed: for index in changed: # update the avg speed for the changed indices # (simpler to do this for all changed indices than also track whether # distance and/or time have changed) distance = self.df['Distance (km)'][index] time = hourMinSecToFloat(self.df['Time'][index]) self.df.at[index, 'Avg. speed (km/h)'] = distance / time self.dataChanged.emit(changed)
def newData(self): dfs = self.data.splitMonths(returnType="CycleData") totals = [(monthYear, [ monthData.summaryString(*args) for args in self.mainWindow.summary.summaryArgs ]) for monthYear, monthData in dfs] idx = self._matchColumn( self.column, [item[0] for item in self.mainWindow.summary.summaryArgs]) try: totals.sort(key=lambda tup: float(tup[1][idx]), reverse=True) except ValueError: totals.sort(key=lambda tup: hourMinSecToFloat(tup[1][idx]), reverse=True) monthYear, summaries = totals[0] self.time, self.distance, _, self.calories, *vals = summaries self.setText() if monthYear != self.monthYear: self.monthYear = monthYear msg = self.makeMessage(monthYear) return msg else: return None
def test_combine_rows(self, setup, qtbot): rng = np.random.default_rng() row = rng.integers(0, len(self.df)) while True: replace = rng.integers(0, len(self.df), size=3) if row not in replace: break names = self.df.columns expected = {name: self.df.iloc[row][name] for name in names} expected['Time'] = hourMinSecToFloat(parseDuration(expected['Time'])) for idx in replace: self.df.at[idx, 'Date'] = expected['Date'] self.df.at[idx, 'Gear'] = expected['Gear'] expected['Distance (km)'] += self.df.at[idx, 'Distance (km)'] expected['Calories'] += self.df.at[idx, 'Calories'] expected['Time'] += hourMinSecToFloat( parseDuration(self.df.at[idx, 'Time'])) expected[ 'Avg. speed (km/h)'] = expected['Distance (km)'] / expected['Time'] expected['Time'] = floatToHourMinSec(expected['Time']) data = CycleData(self.df) date = expected['Date'].strftime("%d %b %Y") with qtbot.waitSignal(data.dataChanged): data.combineRows(date) df = data.df[data.df['Date'] == expected['Date']] assert len(df) == 1 for name in names: try: float(df.iloc[0][name]) except: assert df.iloc[0][name] == expected[name] else: assert np.isclose(df.iloc[0][name], expected[name])
def knownData(): dates = [f"2021-04-{i:02}" for i in range(26, 31)] dates += [f"2021-05-{i:02}" for i in range(1, 6)] dct = {'Date':dates, 'Time':["00:53:27", "00:43:04", "00:42:40", "00:43:09", "00:42:28", "00:43:19", "00:42:21", "00:43:04", "00:42:11", "00:43:25"], 'Distance (km)':[30.1, 25.14, 25.08, 25.41, 25.1, 25.08, 25.13, 25.21, 25.08, 25.12], 'Gear':[6]*10} dct['Calories'] = [d*14.956 for d in dct['Distance (km)']] times = np.array([hourMinSecToFloat(t) for t in dct['Time']]) dct['Avg. speed (km/h)'] = dct['Distance (km)'] / times return dct
def combineRows(self, date): """ Combine all rows in the dataframe with the given data. """ i0, *idx = self.df[self.df['Date'] == parseDate( date, pd_timestamp=True)].index combinable = ['Time', 'Distance (km)', 'Calories', 'Avg. speed (km/h)'] for i in idx: for name in combinable: if name == 'Time': t0 = hourMinSecToFloat( parseDuration(self.df.iloc[i0][name])) t1 = hourMinSecToFloat(parseDuration( self.df.iloc[i][name])) newValue = floatToHourMinSec(t0 + t1) self.df.at[i0, name] = newValue elif name == 'Avg. speed (km/h)': self.df.at[i0, name] = self.df['Distance (km)'][ i0] / hourMinSecToFloat(self.df['Time'][i0]) else: self.df.at[i0, name] += self.df.iloc[i][name] self.df.drop(idx, inplace=True) self.dataChanged.emit(i0)
def append(self, dct): """ Append values in dict to DataFrame. """ if not isinstance(dct, dict): msg = f"Can only append dict to CycleData, not {type(dct).__name__}" raise TypeError(msg) times = np.array([hourMinSecToFloat(t) for t in dct['Time']]) dct['Avg. speed (km/h)'] = dct['Distance (km)'] / times tmpDf = pd.DataFrame.from_dict(dct) tmpDf = pd.concat([self.df, tmpDf], ignore_index=True) index = tmpDf[~tmpDf.isin(self.df)].dropna().index self.df = tmpDf self.df.sort_values('Date', inplace=True) self.dataChanged.emit(index)
def test_set_summary_criteria(self, setupKnownData, qtbot, variables): self.prefDialog.pagesWidget.setCurrentIndex(1) pbPref = self.prefDialog.pagesWidget.widget(1) aliases = {'Distance':'Distance (km)', 'Speed':'Avg. speed\n(km/h)'} funcs = {'sum':sum, 'min':min, 'max':max, 'mean':np.mean} for name, comboBox in pbPref.summaryComboBoxes.items(): num = comboBox.currentIndex() while num == comboBox.currentIndex(): num = random.randrange(0, comboBox.count()) comboBox.setCurrentIndex(num) with qtbot.waitSignal(self.viewer.viewerUpdated, timeout=variables.longWait): pbPref.apply() measure = comboBox.currentText() viewerName = aliases.get(name.capitalize(), name.capitalize()) viewerNameNoNewline = re.sub(r"\n", " ", viewerName) col = self.viewer.headerLabels.index(viewerName) # known data is from April and May 2021 groups = self.data.df.groupby(self.data.df['Date'] <= pd.Timestamp(year=2021, month=4, day=30)) qtbot.wait(variables.shortWait) idx = 0 for _, df in groups: data = df[viewerNameNoNewline] if name == 'time': data = np.array([hourMinSecToFloat(t) for t in data]) expected = funcs[measure](data) if name == 'time': expected = floatToHourMinSec(expected) expected = self.data.fmtFuncs[viewerNameNoNewline](expected) assert self.viewer.topLevelItems[idx].text(col) == expected idx += 1
def test_pb_month_criterion(self, setup, qtbot): self.prefDialog.pagesWidget.setCurrentIndex(1) pbPref = self.prefDialog.pagesWidget.widget(1) indices = list(range(pbPref.bestMonthCriteria.count())) random.shuffle(indices) if indices[0] == pbPref.bestMonthCriteria.currentIndex(): val = indices.pop(0) indices.append(val) keys = {"Distance":"Distance (km)", "Avg. speed":"Avg. speed (km/h)"} for idx in indices: pbPref.bestMonthCriteria.setCurrentIndex(idx) button = self.prefDialog.buttonBox.button(QDialogButtonBox.Apply) with qtbot.waitSignal(button.clicked, timeout=10000): qtbot.mouseClick(button, Qt.LeftButton) criterion = pbPref.bestMonthCriteria.currentText() column = keys.get(criterion, criterion) months = self.data.splitMonths() if column in ["Distance (km)", "Calories"]: values = [(monthYear, sum(df[column])) for monthYear, df in months] elif column == "Time": values = [(monthYear, sum([hourMinSecToFloat(parseDuration(t)) for t in df[column]])) for monthYear, df in months] elif column == "Avg. speed (km/h)": values = [(monthYear, max(df[column])) for monthYear, df in months] else: values = [(monthYear, np.around(np.mean(df[column]))) for monthYear, df in months] values.sort(key=lambda item: item[1], reverse=True) best = values[0] assert self.app.pb.bestMonth.monthYear == best[0]
def test_float_to_hms(value): s = floatToHourMinSec(value) value2 = hourMinSecToFloat(s) assert np.isclose(value, value2, atol=1 / 3600) # tolerance within one second
def test_edit_remove_rows(self, setupKnownData, qtbot, monkeypatch): edit = ['2021-04-26', '2021-04-28', '2021-05-04'] remove = ['2021-04-30', '2021-05-03'] selectDates = [ datetime.strptime(d, '%Y-%m-%d').strftime("%d %b %Y") for d in edit ] selectDates += [ datetime.strptime(d, '%Y-%m-%d').strftime("%d %b %Y") for d in remove ] for topLevelItem in self.widget.topLevelItems: for idx in range(topLevelItem.childCount()): item = topLevelItem.child(idx) if item.text(0) in selectDates: item.setSelected(True) dialogEdit = { 0: { 'Calories': 450.2, 'Date': pd.Timestamp('2021-04-16 00:00:00'), 'Distance (km)': 30.1, 'Gear': 6, 'Time': '00:53:27' }, 2: { 'Calories': 375.1, 'Date': pd.Timestamp('2021-04-28 00:00:00'), 'Distance (km)': 42.3, 'Gear': 6, 'Time': '01:00:05' }, 8: { 'Calories': 375.1, 'Date': pd.Timestamp('2021-05-04 00:00:00'), 'Distance (km)': 25.08, 'Gear': 6, 'Time': '00:42:11' } } dialogRemove = [7, 4] monkeypatch.setattr(EditItemDialog, "exec_", lambda *args: QDialog.Accepted) monkeypatch.setattr(EditItemDialog, "getValues", lambda *args: (dialogEdit, dialogRemove)) with qtbot.waitSignals([self.parent.data.dataChanged] * 2): self.widget._editItems() for d in remove: assert pd.Timestamp(datetime.strptime( d, '%Y-%m-%d')) not in self.parent.data['Date'] for dct in dialogEdit.values(): row = self.parent.data.df.loc[self.parent.data.df['Date'] == dct['Date']] for col in dct.keys(): assert row[col].values[0] == dct[col] # check that speed has updated for row where time and distance were changed dct = dialogEdit[2] expected = dct['Distance (km)'] / hourMinSecToFloat(dct['Time']) row = self.parent.data.df.loc[self.parent.data.df['Date'] == dct['Date']] speed = row['Avg. speed (km/h)'].values[0] assert np.isclose(speed, expected)