数据集
数据集由三部分组成:样本数据集samples,设备数据集devices和app处理数据集app_processes
题目描述
此次的题目是从3个组里面选择2个组,6个小题里面选择4个小题,来进行作答
英文的话是这样
Group 1
1.1. Determine the ten brands of devices that, according to the Greenhub dataset, appear in more samples. Do the same for device models. What are the most prevalent models in the dataset, in terms of presence in the samples? Are there cases of brands that appear in the list under different names?
1.2. Create two pie charts, one showing the slices corresponding to the ten brands and one chart where the slices correspond to the proportions for the ten models. Include in each pie chart an additional slice corresponding to the other brands and models. The radius of this other slice should, of course, be representative of the percentage of the samples that corresponds to the devices of other models and brands.
Group 2
2.1. Determine the ten apps that are not system apps (is_system_app
== 1) that appear more often according to the dataset. Use the name
of the apps but, when reporting the results, present both the name
and the application_label
.
2.2. Establish the twenty smartphone brands with the most models in the dataset. From those, select all the brands that are Chinese and those that are South Korean. Now contrast the apps that appear more often in samples associated with those brands (similarly to the previous task, but constrained to these brands) with the most popular apps running on devices of South Korean brands.
Group 3
3.1. When studying the energy impact of different apps, we are usually interested in discharge sequences. A discharge sequence is a sequence of samples where the device_id
is the same, timestamps are contiguous, and every sample in the sequence has battery_state
== 'Discharging'
. You can identify the beginning of a discharge sequences by identifying moments (between two consecutive samples for the same device_id
) when battery_state
transitioned from any other state (Full
, Not Charging
, or Charging
) to Discharging
and the end of a sequence when consecutive samples for the same device_id
transition from Discharging
to any other state. Build code to organize the samples in dataset-samples so as to associate discharge sequence codes (you’re responsible for creating a sequence of them) to the ids of the samples in the corresponding discharge sequence. What is the length of the longest discharge sequence, in terms of the difference between the timestamp of the last sample in the sequence and the timestamp of the first one.
3.2. Determine the 10 apps (again, not system apps) that appear more often in discharge sequences.
初步的话,看着组1、2是比较简单的,就选这俩了
第1组
1.1 根据Greenhub数据集,确定在所有样本中出现次数最多的设备的10个brands。对设备models执行相同的操作。就样本中的存在而言,数据集中最流行的model是什么?列表中是否有使用不同名称出现的品牌案例?
1.2 创建两个饼图,一个饼图显示与十个brands相对应的切片,而另一个饼图与十个models的比例相对应。在每个饼图中都包含一个与其他品牌和型号相对应的附加切片。当然,此其他切片的半径应代表与其他型号和品牌的设备相对应的样本百分比。
第2组
2.1 根据数据集确定出现频率更高(最高)的十个非系统应用(is_system_app
== 1)。将应用程序的name
与频率结合,但是在报告(在report上展示)结果时,要同时显示name
和application_label
。
根据数据集的确定name
出现频率更高(最高)的十个非系统应用(is_system_app
== 0)
2.2 建立数据集中models最多的20个智能手机brands。从这些中,选择所有Chinese brands和South Korean brands。现在,将与这些brands相关的samples中出现频率更高的apps(与之前的任务类似,但仅限于这些brands)与在South Korean brands的设备上运行的最受欢迎的apps进行对比。
题目分析
GROUP1
1.1
题目描述的不是很清晰,说实在的,有点歧义存在的。在devices数据集里面,对于每一个安卓手机都有其对应的id以及对应的信息:model
;manufacturer
;brand
;os_version
;is_root;created_at
。
但是,他说
Determine the ten brands of devices that, according to the Greenhub dataset, appear in more samples.
这个品牌在越多的样本中出现,那么它就越会成为这top10中的一员,那么这个samples样本指的是什么?是仅仅统计各个参与Greenhub统计的手机信息中的品牌,还是统计在samples或(逻辑或)在app_processes数据集中含有该品牌数据字段的数量呢?
往简单的方面想,仅仅是统计前者吧,如果是后者,需要将对应id映射到brand,而且要读取异常大的数据量。
考虑到他在说明文档最后对report撰写要求中提到的:
An explanation about how the team scaled up the solution so as to deal with the large size of the dataset, specially for groups 2 and 3.
他要做题者解释一下如何处理大数据,特别是对GROUP2和3,在这儿他没有提起GROUP1,那么题目1.1的解法极大概率是分析的前者情况了。
但是,后来一想,存在这个samples数据集,而且数据量为165个文件,并不是非常大,而且如果仅仅分析前者非常简单,不像是团队项目要完成的任务量。
综上所述,要对每一个id对应的brand映射好,构造brand字典,遍历samples数据,将数据统计到brand字典当中。
Are there cases of brands that appear in the list under different names?
后来外教又声明了一下:
About the question “Are there cases of brands that appear in the list under different names?”, what I want to know is if there are smartphone manufacturers in the dataset that appear with similar but different names? If not, tell me so. If yes, which ones?
这句话就是说有没有手机制造商相似(同)但是名字却不同?
我认为这句话就是说,是否同一个品牌以不同的名字出现。存在。因为有HUAWEI和Huawei,这个可以用统一大写和不进行操作得到,操作之后brand变多了!这个几乎和2.2的第一小问解法一样。
1.2
要做两个饼图,
饼图1:10个top10以及1个other的brands(的比例)信息;
饼图2:10个top10以及1个other的models(的比例)信息。
其中对于slice的解释,在WordNet上搜索,
piece, slice – (a serving that has been cut from a larger portion; “a piece of pie”; “a slice of bread”)
这里的slice解释可以为扇区,就是饼图的一块,一部分。
其中最难理解的是对radius的解释,按常理这应该翻译为半径,但是饼图不是按照面积大小来反映比例吗?怎么成半径了?难不成是玫瑰图?在网上搜了,玫瑰图是饼图的变种图,所以说可以说玫瑰图是饼图。在知乎上搜了搜:
起初还想radius是不是弧度,如果是弧度的话就是纯饼图了,但是弧度的英文是radian,那么此题就是要画玫瑰图,形状为上图的左上图。
由于涉及到了其他项,把数据总量当成分母进行运算即可。
GROUP1
2.1
数据处理是针对app_processes数据集。
根据布尔表达式is_system_app
== 1来确定是否保留字段,字段属性值仅保留name
和application_label
,根据个人猜测,name
和application_label
是一一对应的,那么,如要减少内存占用量,可先将映射关系保存在字典中。
但是name
和application_label
不是一一对应的,比如说存在:
com.google.android.music:main;Google Play Music
com.google.android.music:main;Google Play Música
也就是说一个name
如com.google.android.music:main对应了多于一个application_label
如Google Play Music和Google Play Música。
2.2
又是意义不明确的一句:
Establish the twenty smartphone brands with the most models in the dataset.
the dataset到底是哪个数据集呀?这儿个人的分析是devices数据集,就是仅仅用该数据集创造一个{brands:num of models}
的字典,字典的键是智能手机品牌,值是该品牌拥有的不同型号(因为存在一个品牌的同一型号出现多次)数量。
其次,要构造出{brands:countries}
的字典,但是搜了三个数据集的属性值,仅有的是samples数据集中的timezone
和country_code
属性,其中存在这样的值:
America/Chicago;us
Europe/Lisbon;pt
但是你想,这是时区和其对应的国家代码,有可能中国生产的手机但是美国人在用啊!所以我认为品牌是中国还是韩国的还是要在网络上进行搜索并构造出映射表。因为仅仅有top20的品牌,所以比较容易遍历完。
做题
2.1
对于将两个字典进行合并是很重要的,而且不光是要求并集,而且要把相同的数值进行相加处理:
def sum_dict(a,b):
temp = dict()
for key in a.keys()| b.keys():
temp[key] = sum([d.get(key, 0) for d in (a, b)])
return temp
2.2.1
path = '../dataset-devices/devices.query.1.csv'
df = pd.read_csv(path,sep=';')
df.head()
可见
思路是:把brand和model抽取出来,进行去重处理,统计每个brand的数量就是每个brand所拥有的model数量。
于是构造出一个函数:
def get_top20_brand_model_type_counts():
path = '../dataset-devices/'
frames=[]
for file in os.listdir(path):
path_f = path+file
df = pd.read_csv(path_f,sep=';')
data = df.loc[:,['model','brand']]
frames.append(data)
data = pd.concat(frames)
return data.drop_duplicates().brand.value_counts()[:20]
查看一下结果:
get_top20_brand_model_type_counts()
samsung 1321
lge 534
alps 496
TCL 418
HUAWEI 378
ZTE 286
Lenovo 268
htc 195
Sony 193
TECNO 188
asus 187
Micromax 186
OPPO 179
BLU 173
QMobile 154
motorola 153
Huawei 152
vivo 143
Android 141
ADVAN 121
Name: brand, dtype: int64
可以看到HUAWEI和Huawei是同一个品牌啊,但是他们却分成了不一样的类别,所以应该提前进行数据清洗,把所有的改成大写或者改成小写。
在进行去重之前,首先对model和brand列进行全大写归一化处理:
data.model=data.model.apply(lambda x:str(x).upper())
data.brand=data.brand.apply(lambda x:str(x).upper())
之后运算的结果为:
SAMSUNG 1392
LGE 533
HUAWEI 531
ALPS 495
TCL 418
ZTE 302
LENOVO 269
TECNO 220
HTC 200
SONY 193
MICROMAX 190
ASUS 189
OPPO 179
BLU 175
QMOBILE 164
MOTOROLA 156
LAVA 156
ANDROID 149
VIVO 144
INTEX 138
Name: brand, dtype: int64
其实这样算出来是错误的,三十多万个数据怎么这么少?在写其他代码的时候发现了这一点,就是索引不一致的问题,在concat
合并的时候一定要:
data = pd.concat(frames).reset_index()
其中还有个drop参数。
最后的结果是这样的:
SAMSUNG 84342
XIAOMI 28558
OPPO 22095
VIVO 15454
HUAWEI 14709
MOTOROLA 12741
LGE 10909
LENOVO 8745
ASUS 5533
TCL 5189
TECNO 5113
ITEL 4572
MICROMAX 4227
ZTE 3659
NOKIA 3639
ADVAN 3571
LAVA 3537
INFINIX 3033
HONOR 2760
SONY 2712
Name: brand, dtype: int64
其实这样也是错误的,原因:
虽然reset了index,但是index这一列没有删除,这就导致去重无效。
SAMSUNG 1392
LGE 533
HUAWEI 531
ALPS 495
TCL 418
ZTE 302
LENOVO 269
TECNO 220
HTC 200
SONY 193
MICROMAX 190
ASUS 189
OPPO 179
BLU 175
QMOBILE 164
LAVA 156
MOTOROLA 156
ANDROID 149
VIVO 144
INTEX 138
Name: brand, dtype: int64
所以说结果是没错的。
还有一点,有着HUAWEI.这样的brand,可以用如下函数对比发现:
# only keep upper characters and numbers
def drop_duplicate(x):
temp=''
for i in x:
if 48<=ord(i)<=57 or 65<=ord(i)<=90:
temp+=i
return temp
再对brand操作一下:
data.brand=data.brand.apply(drop_duplicate)
得到的结果:
SAMSUNG 1392
LGE 533
HUAWEI 532 <- 531
ALPS 495
TCL 418
ZTE 302
LENOVO 269
TECNO 220
HTC 200
SONY 194 <- 193
MICROMAX 190
ASUS 189
OPPO 179
BLU 175
QMOBILE 168 <- 164
MOTOROLA 156
LAVA 156
ANDROID 149
VIVO 144
INTEX 138
Name: brand, dtype: int64
2.2.1的完整代码如下:
import pandas as pd
import os
def get_top20_brand_model_type_counts():
path = '../dataset-devices/'
frames=[]
for file in os.listdir(path):
path_f = path+file
df = pd.read_csv(path_f,sep=';')
data = df.loc[:,['model','brand']]
frames.append(data)
data = pd.concat(frames).reset_index()
data.model=data.model.apply(lambda x:str(x).upper())
data.brand=data.brand.apply(lambda x:str(x).upper())
return data.drop_duplicates().brand.value_counts()[:20]
经过在网络上查找,可以获取到品牌对应的国家:
southKorean=['SAMSUNG','LGE']
Chinese=['XIAOMI','OPPO','VIVO','HUAWEI','LENOVO','ASUS','TCL','TECNO','ITEL','ZTE','INFINIX','HONOR']
经过更改,是这样的:
Korean=['SAMSUNG','LGE']
Chinese=['HUAWEI','TCL','ZTE','LENOVO','TECNO','HTC','ASUS','OPPO','VIVO']
2.2.2
要构造出来一个手机id与生产国家对应的,如1:‘China’,3:‘southKorean’,这样的;然后再在app_processes里面进行遍历与统计,类似于2.1。
samples里面的id对应app_processes里面的sample_id
samples里面的device_id对应devices里面的id
想要知道app_processes里面的sample_id对应什么device_id,从而知道是什么品牌
app_processes里面有id;sample_id,id用不着,找sample_id到device_id的映射
就是找samples里面的id到他的device_id的映射
取smaples里的id和device_id这两列再搞映射关系即可
首先,要在devices-dataset里面获取到device_id和国家名的映射关系,因为samples-dataset里面有id和device_id,所以根据上面获得的映射关系,将device_id映射到国家,可以获得id和国家的映射关系,也就是sample_id和国家的映射关系。
之后遍历app_processes-dataset中的所有文件,首先筛选出非系统文件,取出sample_id列和name列,将之前的sample_id-country映射字典应用在sample_id列上,再以中国和韩国为不同的统计对象进行应用名的统计即可,且在文件的不断访问中不断拼接统计的数据。
最后得到的数据分别是中国、韩国的前十个出现次数最多的非系统应用,对于相同的应用名,采用饼图进行对比。
def get_sampleid_deviceid_map():
path = '../samples/'
res_dict = {}
for file in os.listdir(path):
path_f = path+file
df = pd.read_csv(path_f,sep=';')
res_dict = {**res_dict,**dict(df.loc[:,['id','device_id']].values)}
return res_dict
这个文件太大了,而且没有必要把所有的映射都弄上去,只弄韩国和中国的就行了,要不然非常大,而且非常耗时,这需要结合布尔表达式在每次处理文件的时候筛掉。
所以进行了精简
%%time
southKorean=['SAMSUNG','LGE']
Chinese=['XIAOMI','OPPO','VIVO','HUAWEI','LENOVO','ASUS','TCL','TECNO','ITEL','ZTE','INFINIX','HONOR']
# process daevices
path = '../dataset-devices/'
frames=[]
for file in os.listdir(path):
path_f = path+file
df = pd.read_csv(path_f,sep=';')
data = df.loc[:,['id','brand']]
frames.append(data)
data = pd.concat(frames).reset_index(drop=True)
data.brand=data.brand.apply(lambda x:str(x).upper())
deviceid_country_map=dict(zip(data.id.values,data.brand.apply(conf_country).values))
# process samples
path2 = '../samples/'
res_dict = {}
for file in os.listdir(path2)[:20]:
path_f2 = path2+file
df2 = pd.read_csv(path_f2,sep=';')
df2=df2.loc[:,['id','device_id']]
df2.device_id=df2.device_id.map(deviceid_country_map)
processed_dict=dict(zip(df2.dropna().id,df2.dropna().device_id))
res_dict = {**res_dict,**processed_dict}
读取完20个文件需要29.5 s,而且用
float(sys.getsizeof(res_dict))/(2**20)
计算得到的字典的内存占用量仅为160MB,那么再测试一下全部165个文件的情况。
全部文件用时5min 30s,占用1280MB、1.25GB内存,可以接受。
这几乎和之前得到的相差了20000000个数值。
但是结果仍然很离谱,如果按照这个速度仅仅进行映射,需要跑27个小时才能跑完…
令人非常兴奋的是,我找到了一个解法!这么长的时间,一猜就是进行了遍历,就是说,要遍历完字典中所有的值才会结束。但是直接在字典中索引一个值是不会消耗很长的时间的,所以:
def deom(x):
try:
return res_dict[x]
except KeyError:
return None
path6='../app_processes/app_processes.query.1.csv'
df6=pd.read_csv(path6,sep=';')
df7=df6[df6.is_system_app==0]
df7.sample_id.apply(deom)
对比一下:
这是何等的差距!
df8=df7.copy()
df8.sample_id=df8.sample_id.apply(deom)
df8[~df8.sample_id.isna()]
在测试中出现了一个报错:
A value is trying to be set on a cop`在这里插入代码片`y of a slice from a DataFrame
这个处理方式是用data2=data1.copy()
,然后操作data2。
如下代码是获取映射字典的函数:
def conf_country(x):
if x in southKorean:
return 'Korean'
elif x in Chinese:
return 'Chinese'
else:
return None
# 获取sample_id,country映射字典:
def get_sampleid_country_map():
southKorean=['SAMSUNG','LGE']
Chinese=['XIAOMI','OPPO','VIVO','HUAWEI','LENOVO','ASUS','TCL','TECNO','ITEL','ZTE','INFINIX','HONOR']
# process daevices
path = '../dataset-devices/'
frames=[]
for file in os.listdir(path):
path_f = path+file
df = pd.read_csv(path_f,sep=';')
data = df.loc[:,['id','brand']]
frames.append(data)
data = pd.concat(frames).reset_index(drop=True)
data.brand=data.brand.apply(lambda x:str(x).upper())
deviceid_country_map=dict(zip(data.id.values,data.brand.apply(conf_country).values))
# process samples
path2 = '../samples/'
res_dict = {}
for file in os.listdir(path2):
path_f2 = path2+file
df2 = pd.read_csv(path_f2,sep=';')
df2=df2.loc[:,['id','device_id']]
df2.device_id=df2.device_id.map(deviceid_country_map)
processed_dict=dict(zip(df2.dropna().id,df2.dropna().device_id))
res_dict = {**res_dict,**processed_dict}
return res_dict
以下是完整的操作中国和韩国品牌对应的非系统应用使用频率情况:
def sum_dict(a,b):
temp = dict()
for key in a.keys()| b.keys():
temp[key] = sum([d.get(key, 0) for d in (a, b)])
return temp
# 在使用之前要注意res_dict是否被定义,res_dict=get_sampleid_country_map()
def map_func(x):
try:
return res_dict[x]
except KeyError:
return None
def app_name_CN_KR(start=1,num=10,allData=False):
path = '../app_processes/'
all_path_list = [path+i for i in os.listdir(path)]
ChinaDict = {}
KoreanDict = {}
for file in all_path_list if allData==True else all_path_list[start-1:start-1+num]:
df = pd.read_csv(file,sep=';',error_bad_lines=False, warn_bad_lines=False)
data = df[df.is_system_app==0]
data2=data.copy()
data2.sample_id=data2.sample_id.apply(map_func)
data2=data2[~data2.sample_id.isna()]
ChinaDict = sum_dict(ChinaDict,dict(data2[data2.sample_id=='Chinese'].name.value_counts()))
KoreanDict = sum_dict(KoreanDict,dict(data2[data2.sample_id=='Korean'].name.value_counts()))
ChinaDict = dict(sorted(ChinaDict.items(),key=lambda x:x[1],reverse=True))
KoreanDict = dict(sorted(KoreanDict.items(),key=lambda x:x[1],reverse=True))
return (list(ChinaDict)[:10], list(KoreanDict)[:10])
操作100个文件需要32.2s,粗略计算,5912个文件,总共32.2*(5912/100)=1903.664s,31.73min。
又从第3000个文件往后读取了200个文件:
其实如果代码是正确的,在jupyter notebook上可以忽略提示,在网上查找是使用如下代码:
import warnings
warnings.filterwarnings('ignore')
但是没用。
还有人说可以这样用:
pd.option.mode.chained_assignment = None
但是:
module 'pandas' has no attribute 'option'
经过查找发现read_csv
里面还有一个参数可以使用:
data = pd.read_csv(path, error_bad_lines=False, warn_bad_lines=False)
中韩的结果:
({'com.mansoon.BatteryDouble': 7048682,
'com.google.android.googlequicksearchbox:interactor': 5614711,
'com.google.android.googlequicksearchbox:search': 4320737,
'com.google.android.gms.persistent': 4037772,
'org.simalliance.openmobileapi.service:remote': 3127717,
'com.facebook.orca': 2565712,
'com.facebook.lite:fbns': 2165906,
'com.whatsapp': 2130342,
'com.facebook.katana': 2121389,
'com.instagram.android:mqtt': 1884579},
{'com.mansoon.BatteryDouble': 17313967,
'com.google.android.googlequicksearchbox:search': 14933720,
'com.google.android.googlequicksearchbox:interactor': 14228639,
'org.simalliance.openmobileapi.service:remote': 11804923,
'com.facebook.orca': 7960282,
'com.google.android.gms.persistent': 6953279,
'com.instagram.android:mqtt': 6149233,
'com.android.systemui.recents': 5069114,
'com.whatsapp': 4827753,
'com.sec.spp.push:RemoteDlcProcess': 4811125})
大数据的处理方法:每一次只打开一个文件,只通过这个文件获取到想要的东西,然后再读取另一个文件的时候将获得到的相似的东西通过某些特定的函数整合到一起,保存在内存中,经过这样不断迭代,直到读取完所有的文件。这样的好处是内存占用很少,但是限于文件的数量、大小、cpu的速度、硬盘的读取速度,读取并操作完所有的文件的速度还是很慢的。