Skip to content

Instantly share code, notes, and snippets.

@tvwerkhoven
Created November 6, 2011 22:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tvwerkhoven/1343678 to your computer and use it in GitHub Desktop.
Save tvwerkhoven/1343678 to your computer and use it in GitHub Desktop.
iPhone 3GS battery life analysis & plots
These files can be used to analyse the battery usage of an iPhone 3GS under real-life usage.
iphone_battery.csv: measurement data
proc_iphone_batt.py: process & plot data
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