iPhone 3GS battery life analysis & plots
We can make this file beautiful and searchable if this error is corrected: It looks like row 3 should actually have 1 column, instead of 4. in line 2.
# iphone battery | |
# 20111001 Tim | |
# screenshot fname, usage time [hh:mm], standby time [hh:mm], battery [%] | |
IMG_0001.PNG, 0:59, 1:00, 92 | |
IMG_0002.PNG, 0:59, 1:04, 92 | |
IMG_0003.PNG, 1:53, 2:08, 82 | |
IMG_0004.PNG, 1:54, 2:20, 81 | |
IMG_0005.PNG, 2:00, 2:53, 80 | |
IMG_0006.PNG, 2:15, 3:44, 76 | |
IMG_0007.PNG, 2:18, 4:00, 75 | |
IMG_0008.PNG, 2:36, 5:55, 72 | |
IMG_0009.PNG, 2:57, 9:15, 66 | |
IMG_0010.PNG, 3:23, 16:46, 53 | |
IMG_0011.PNG, 4:01, 18:43, 45 | |
IMG_0012.PNG, 0:00, 0:07, 100 | |
IMG_0013.PNG, 0:19, 1:18, 95 | |
IMG_0014.PNG, 0:34, 2:10, 92 | |
IMG_0015.PNG, 0:45, 3:06, 90 | |
IMG_0016.PNG, 0:52, 4:22, 88 | |
IMG_0017.PNG, 1:13, 5:47, 83 | |
IMG_0018.PNG, 1:15, 6:08, 83 | |
IMG_0019.PNG, 1:43, 8:57, 77 | |
IMG_0020.PNG, 2:01, 10:57, 71 | |
IMG_0021.PNG, 2:32, 12:07, 66 | |
IMG_0022.PNG, 2:36, 12:21, 63 | |
IMG_0023.PNG, 2:40, 13:02, 61 | |
IMG_0024.PNG, 3:32, 16:31, 50 | |
IMG_0025.PNG, 2:58, 15:32, 57 | |
IMG_0026.PNG, 0:03, 0:46, 100 | |
IMG_0027.PNG, 0:14, 2:47, 100 | |
IMG_0028.PNG, 0:22, 2:55, 98 | |
IMG_0029.PNG, 0:01, 0:03, 100 | |
IMG_0030.PNG, 0:03, 0:19, 100 | |
IMG_0031.PNG, 0:38, 2:15, 96 | |
IMG_0032.PNG, 1:04, 3:24, 90 | |
IMG_0033.PNG, 0:36, 3:18, 90 | |
IMG_0034.PNG, 0:44, 4:13, 89 | |
IMG_0035.PNG, 1:31, 6:19, 80 | |
IMG_0036.PNG, 0:22, 0:51, 99 | |
IMG_0037.PNG, 0:39, 2:15, 95 | |
IMG_0038.PNG, 0:51, 2:31, 91 | |
IMG_0039.PNG, 1:18, 6:31, 86 | |
IMG_0043.PNG, 1:35, 8:46, 81 | |
IMG_0047.PNG, 2:44, 15:52, 62 | |
IMG_0048.PNG, 0:57, 1:44, 92 | |
IMG_0052.PNG, 1:11, 2:01, 89 | |
IMG_0053.PNG, 1:27, 2:35, 85 | |
IMG_0054.PNG, 1:43, 3:12, 83 | |
IMG_0055.PNG, 2:06, 4:01, 78 | |
IMG_0056.PNG, 2:10, 4:10, 78 | |
IMG_0057.PNG, 2:16, 4:46, 77 | |
IMG_0058.PNG, 2:31, 5:27, 72 | |
IMG_0060.PNG, 3:02, 6:56, 63 | |
IMG_0061.PNG, 3:05, 7:26, 61 | |
IMG_0062.PNG, 3:30, 7:51, 58 | |
IMG_0063.PNG, 3:40, 9:02, 57 | |
IMG_0065.PNG, 4:11, 11:00, 52 | |
IMG_0066.PNG, 5:19, 16:10, 35 | |
IMG_0067.PNG, 0:03, 0:08, 100 | |
IMG_0068.PNG, 0:28, 0:34, 95 | |
IMG_0073.PNG, 2:04, 3:22, 80 | |
IMG_0074.PNG, 2:40, 5:12, 72 | |
IMG_0076.PNG, 3:20, 8:36, 59 | |
IMG_0080.PNG, 4:27, 12:07, 48 | |
IMG_0083.PNG, 5:48, 17:08, 20 | |
IMG_0084.PNG, 5:55, 18:22, 18 | |
IMG_0085.PNG, 0:03, 0:30, 100 | |
# 3G starts | |
IMG_0086.PNG, 1:27, 3:05, 82 | |
IMG_0087.PNG, 1:47, 4:09, 76 | |
IMG_0088.PNG, 2:50, 6:40, 60 | |
IMG_0090.PNG, 0:11, 0:47, 97 | |
IMG_0091.PNG, 0:23, 1:52, 95 | |
IMG_0092.PNG, 1:13, 3:03, 80 | |
IMG_0093.PNG, 1:54, 5:57, 68 | |
IMG_0094.PNG, 2:06, 6:15, 62 | |
IMG_0095.PNG, 2:24, 10:14, 58 | |
IMG_0096.PNG, 0:31, 0:54, 94 | |
IMG_0110.PNG, 4:04, 13:35, 40 | |
IMG_0112.PNG, 5:14, 16:22, 19 | |
IMG_0125.PNG, 2:44, 13:21, 59 | |
IMG_0127.PNG, 0:05, 1:02, 98 | |
IMG_0128.PNG, 0:42, 1:27, 93 | |
# iOS 5 | |
IMG_0164.PNG, 0:51, 2:23, 88 | |
IMG_0165.PNG, 0:19, 0:39, 97 | |
IMG_0167.PNG, 1:07, 3:20, 85 | |
IMG_0168.PNG, 1:35, 6:52, 77 | |
IMG_0173.PNG, 1:32, 6:45, 71 | |
IMG_0174.PNG, 2:17, 18:39, 52 | |
IMG_0175.PNG, 3:10, 22:11, 38 | |
IMG_0177.PNG, 1:48, 7:10, 64 | |
IMG_0179.PNG, 2:59, 13:00, 40 | |
IMG_0195.PNG, 2:41, 20:30, 41 | |
IMG_0196.PNG, 3:01, 22:43, 35 | |
IMG_0219.PNG, 3:37, 15:55, 13 | |
IMG_0220.PNG, 3:50, 14:54, 7 | |
IMG_0224.PNG, 1:43, 7:03, 56 | |
IMG_0231.PNG, 3:23, 17:29, 12 | |
IMG_0232.PNG, 3:43, 19:09, 4 | |
IMG_0246.PNG, 3:05, 12:28, 43 | |
IMG_0257.PNG, 3:37, 16:04, 32 |
#!/usr/bin/env python2.7 | |
# encoding: utf-8 | |
## | |
# @file proc_iphone_batt.py | |
# @brief process iphone battery data | |
# @author Tim van Werkhoven (t.i.m.vanwerkhoven@xs4all.nl) | |
# @date 20111001 | |
# | |
# Created by Tim van Werkhoven on 20111001 | |
# Copyright (c) 2011 Tim van Werkhoven (t.i.m.vanwerkhoven@xs4all.nl) | |
# | |
# This work is licensed under the Creative Commons Attribution-Share Alike 3.0 | |
# Unported License. To view a copy of this license, visit | |
# http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative | |
# Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, | |
# USA. | |
# Import libs | |
import argparse | |
import sys, os | |
import numpy as N | |
import scipy as S | |
import scipy.optimize | |
import pylab as plt | |
# Define some contants | |
AUTHOR = "Tim van Werkhoven (t.i.m.vanwerkhoven@xs4all.nl)" | |
DATE = "20111001" | |
# Start functions | |
def datestr2num(datestr): | |
"""Convert HH:MM string to number of minutes""" | |
datenum = datestr.split(':') | |
return int(datenum[0])*60 + int(datenum[1]) | |
def plot_data(xdat, desc=None, usage=None, standby=None, do_fit=True, clf=True, nsig=3): | |
"""Plot battery usage. | |
If <do_fit>, also fit data and overplot. | |
If <clf>, first clear the plot.""" | |
if (usage == None and standby == None): | |
return | |
if (clf): | |
plt.clf() | |
suptit = "iPhone 3GS battery usage" | |
if (desc): | |
suptit += u" -- " + desc | |
plt.suptitle(suptit, fontsize=12) | |
subtit = u'' | |
plt.xlabel("Time [hrs]") | |
plt.ylabel("Battery [%]") | |
plt.ylim(0, 100) | |
plt.xlim(0, 35) | |
if (usage != None): | |
plt.plot(usage, xdat, 'b+', label='Usage') | |
if (do_fit): | |
p1, s1 = fit_lindat(xdat, usage) | |
print "Fit %s: A=%g ± %g" % (desc, p1, s1) | |
subtit += u"Usage: %.3g ± %.3g hrs, " % (100.0/p1, nsig*100*s1/(p1**2.)) | |
# Plot fits, with +- nsig sigma bounds | |
plot_fit(p1, s1, nsig, 'b-') | |
if (standby != None): | |
plt.plot(standby, xdat, 'g.', label='Standby') | |
if (do_fit): | |
p1, s1 = fit_lindat(xdat, standby) | |
print "Fit %s: A=%g ± %g" % (desc, p1, s1) | |
subtit += u"Standy: %.3g ± %.3g hrs, " % (100.0/p1, nsig*100*s1/(p1**2.)) | |
plot_fit(p1, s1, nsig, 'g-') | |
subtit += "errors: %d$\sigma$" % (nsig) | |
plt.title(subtit, fontsize=10) | |
print desc + " " + subtit | |
if (usage != None and standby != None): | |
plt.legend() | |
plt.draw() | |
def plot_fit(par1, sig1, nsig=2, *args): | |
"""Plot the model y = 100 - A*x with a=<par1>+-<nsig>*<sig1>""" | |
fitfunc1 = lambda p, x: 100 - p*x | |
fitx1 = N.linspace(0, 120/(par1-(nsig*sig1)), 100) | |
plt.plot(fitx1, fitfunc1(par1, fitx1), *args) | |
plt.plot(fitx1, fitfunc1(par1+nsig*sig1, fitx1), *args) | |
plt.plot(fitx1, fitfunc1(par1-nsig*sig1, fitx1), *args) | |
def fit_lindat(xdat, ydat): | |
""" | |
Given <xdat> and <ydat>, fit the model y = 100 - A*x. Return the parameter A including error as tuple. | |
""" | |
fitfunc1 = lambda p, x: 100 - p*x | |
fitout1 = S.optimize.curve_fit(fitfunc1, ydat, xdat) | |
# Parameter and standard deviation | |
par1 = fitout1[0][0] | |
sig1 = N.sqrt(fitout1[1][0]) | |
return (par1, sig1) | |
def main(): | |
# Parse & check options | |
(parser, args) = parsopts() | |
# Print some debug output | |
print "Running program %s" % (sys.argv[0]) | |
print args | |
print sys.argv | |
# Load data, convert HH:MM formats to minutes | |
rawdat = N.loadtxt(args.datafile, dtype=int, delimiter=',', converters = {0: lambda x: x[4:-4], 1: datestr2num, 2: datestr2num}) | |
# Sort data per measurement mode | |
set1sl = slice(0, N.argwhere(rawdat[:,0] == 85)[0,0]) | |
set2sl = slice(set1sl.stop, N.argwhere(rawdat[:,0] == 128)[0,0]) | |
set3sl = slice(set2sl.stop, N.argwhere(rawdat[:,0] == 257)[0,0]) | |
print set1sl, set2sl, set3sl | |
rawdatsets = {'All': rawdat, | |
'2G iOS 4': rawdat[set1sl], | |
'3G iOS 4': rawdat[set2sl], | |
'3G iOS 5': rawdat[set3sl]} | |
# Loop over different measurement sets | |
for (key, thisdat) in rawdatsets.iteritems(): | |
print "Processing dataset '%s' shape:" % (key), thisdat.shape | |
# Make aliases for data | |
dat_use = thisdat[:,1]/60. | |
dat_stdby = thisdat[:,2]/60. | |
dat_perc = thisdat[:,3] | |
# Filter different datasets (i.e. strictly monotonic declining data) | |
data_mask = N.where((dat_perc[1:] - dat_perc[:-1]) > 0) | |
data_sets = [] | |
prev_mask = 0 | |
for mask in data_mask[0]: | |
# Only select sets which cover 35% battery usage range | |
if (thisdat[prev_mask, 2] - thisdat[mask, 2] > 30): | |
data_sets.append(thisdat[prev_mask:mask+1]) | |
prev_mask = mask+1 | |
# Plot all data sets | |
fnamebase = "iphone_batt_%s" % key.replace(" ","_") | |
if (args.wofits): | |
plot_data(dat_perc, key, usage=dat_use, standby=dat_stdby, do_fit=False) | |
plt.savefig(fnamebase + "-both.pdf") | |
plot_data(dat_perc, key, usage=dat_use, standby=dat_stdby, do_fit=True) | |
plt.savefig(fnamebase + "-both+fits.pdf") | |
if (args.sep): | |
if (args.wofits): | |
plot_data(dat_perc, key, standby=dat_stdby, do_fit=False) | |
plt.savefig(fnamebase + "-standby.pdf") | |
plot_data(dat_perc, key, standby=dat_stdby, do_fit=True) | |
plt.savefig(fnamebase + "-standby+fits.pdf") | |
if (args.wofits): | |
plot_data(dat_perc, key, usage=dat_use, do_fit=False) | |
plt.savefig(fnamebase + "-use.pdf") | |
plot_data(dat_perc, key, usage=dat_use, do_fit=True) | |
plt.savefig(fnamebase + "-use+fits.pdf") | |
# Plot individual datasets | |
# if (args.indiv): | |
# plt.clf() | |
# for dset in data_sets: | |
# plot_data(dset[:,2], key, usage=dset[:,2]/60., standby=dset[:,1]/60., do_fit=True, clf=False) | |
# | |
# plt.savefig(fnamebase + "-both-sep+fits.pdf") | |
def parsopts(): | |
### Parse program options and return results | |
parser = argparse.ArgumentParser(description='This program parses iphone_batt.txt as csv and plots the results.', epilog='Comments & bugreports to %s' % (AUTHOR)) | |
parser.add_argument('datafile', type=str, | |
default="iphone_batt.txt", | |
help='datafile to process (iphone_batt.txt)') | |
parser.add_argument('--wofits', action='store_true', default=False, | |
help='Also plot data without fits (False)') | |
parser.add_argument('--sep', action='store_true', default=False, | |
help='Also plot usage and standby times seperately (False)') | |
# parser.add_argument('--indiv', action='store_true', default=False, | |
# help='Also plot datasets individually (per charge cycle) (False)') | |
parser.add_argument('--version', action='version', version='%(prog)s 0.1') | |
args = parser.parse_args() | |
# Check & fix some options | |
checkopts(parser, args) | |
# Return results | |
return (parser, args) | |
def checkopts(parser, args): | |
# File should exist | |
if (not os.path.exists(args.datafile) or | |
not os.path.isfile(args.datafile)): | |
print "Error: datafile should exist and be a regular file!" | |
parser.print_usage() | |
exit(-1) | |
if __name__ == "__main__": | |
sys.exit(main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment