【CSDN编者按】大家熟知的电影《泰坦尼克号》,是一部经典的奥斯卡电影,也是一部以真实故事改编而拍的电影。

真实故事中,1912年4月14日,这艘当时世界上体积最庞大、内部设施最豪华的客运轮船泰坦尼克号,与一座冰山相撞,2224名船员及乘客中,逾1500人丧生,其中仅333具罹难者遗体被寻回。

时隔一个世纪之久,如果用编程的角度,来审视这场灾难,会有什么发现呢?今天的文章,正是用编程来研究泰坦尼克号的生还者情况。

作者 | 光城

责编 | 胡巍巍

数据预处理

数据初识

这里主要是对数据进行介绍,下载的数据集分为train与test以及gender_submission,分别是训练集,测试集以及生成提交文件的参考文件。

train与test各列分别为:

PassengerId  乘客ID
Pclass  客舱等级(1/2/3等舱位)
Name  乘客姓名
Sex  性别
Age  年龄
SibSp 兄弟姐妹数或配偶数
Parch 父母数或子女数
Ticket 船票编号
Fare 船票价格
Cabin  客舱号
Embarked  登船港口

导包

import seaborn as sns
from matplotlib import pyplot as plt
import pandas as pd
%matplotlib inline
import warnings
from sklearn.model_selection import train_test_split
warnings.filterwarnings('ignore')

数据导入

这里将训练集与测试集导入,并通过concat将数据整合一块,做个缺失值分析处理。

tannike_train=pd.read_csv('./train.csv')
tannike_test=pd.read_csv('./test.csv')
join_data=pd.concat([tannike_train,tannike_test],ignore_index=True)

数据预处理及特征工程

统计缺失值的代码如下:

join_data.isna().sum()

结果:

Age             263
Cabin          1014
Embarked          2
Fare              1
Name              0
Parch             0
PassengerId       0
Pclass            0
Sex               0
SibSp             0
Survived        418
Ticket            0
dtype: int64

从这个结果中我们发现:

Age与Cabin以缺失值较多,而Fare与Embarked缺失值较少,可以考虑用中位数,均值或者众数等方式解决。

失值处理

fare_mean = tannike_train['Fare'].mean()
tannike_test.loc[pd.isnull(tannike_test.Fare),'Fare'] = fare_mean 
embarked_mode = tannike_train['Embarked'].mode()
tannike_train.loc[pd.isna(tannike_train.Embarked),['Embarked']] = embarked_mode[0]
tannike_train.loc[pd.isna(tannike_train.Age),['Age']] = tannike_train['Age'].mean()
tannike_test.loc[pd.isna(tannike_test.Age),['Age']] = tannike_test['Age'].mean()

查看缺失值,以Embarked为例:

tannike_train.Embarked.isna().sum()

输出为0,则表示缺失值被处理完毕!

数据划分

这里直接划分成训练集与测试集的原因是对比与label数据的误差度。

label = tannike_train['Survived']
tannike_train.drop('Survived',axis=1,inplace=True)

X_train,X_test,Y_train,Y_test = train_test_split(tannike_train,label,test_size = 0.3,random_state = 1)

X_train['Survived'] = Y_train
X_test['Survived'] = Y_test

切分数据为训练集,测试集!

性别与生存

我们先来看一下性别与生存关系:

sex_Sur = pd.crosstab(tannike_train.Sex,tannike_train.Survived)
sex_Sur.rename(columns={0.0:'dead',1.0:'survived'},inplace=True)
sex_Sur

输出:

Survived    dead    survived
Sex        
female    81  233
male    468 109

数据可视化:

# stacked属性设置多个柱状图是否叠加
sex_Sur.plot.bar(stacked=True,color=['#f441f1','#b6f442'])
plt.xticks(rotation=0,size='x-large')
plt.xlabel('')

条状图转为饼图

sex_Sur.plot.pie(subplots=True,figsize=(10,5),colors=('#b6f442','#41d0f4'))

综合上述两图,我们发现女性的存活率要比男性高!Age可以作为一个重要特征!

特征工程处理:将数据转为0,1数据。

tannike_train['Sex'] = tannike_train['Sex'].apply(lambda x: 1 if x == 'male' else 0)
tannike_test['Sex'] = tannike_test['Sex'].apply(lambda x: 1 if x == 'male' else 0)
# one-hot编码
tannike_train = pd.get_dummies(data= tannike_train,columns=['Sex'])
tannike_test = pd.get_dummies(data= tannike_test,columns=['Sex'])

年龄与存活

研究年龄与存活关联:

survived_age=tannike_train[tannike_train.Survived==1]['Age']
dead_age=tannike_train[tannike_train.Survived==0]['Age']
age_frame=pd.concat([survived_age,dead_age],axis=1)
age_frame.columns=['Survived','Dead']
age_frame.head()

输出数据(前5行数据):

Survived    Dead
0    NaN     22.0
1    38.0    NaN
2    26.0    NaN
3    35.0    NaN
4    NaN     35.0

可视化:

# 为避免颜色覆盖,使用alpha通道属性
age_frame.plot(kind='hist',bins=30,alpha=0.3,figsize=(10,6))

上图发现:

年龄低于5岁的,存活率相对较高!而在75岁以后存活率也高,在中间的15到35之间存活率相对较高,其他的就比较低了,我们可以在做特征工程时候,对其做年龄划分处理,比如:低中高等。

特征工程处理:

tannike_train['Small_Age'] = np.int32(tannike_train['Age'] <= 5)  
tannike_train['Old_Age'] = np.int32(tannike_train['Age'] >= 65)  
tannike_train['Middle_Age'] = np.int32((tannike_train['Age'] >= 15) & (tannike_train['Age'] <= 25))

tannike_test['Small_Age'] = np.int32(tannike_test['Age'] <= 5)  
tannike_test['Old_Age'] = np.int32(tannike_test['Age'] >= 65)  
tannike_test['Middle_Age'] = np.int32((tannike_test['Age'] >= 15) & (tannike_test['Age'] <= 25))

名字与存活

首先来统计一下各个名字开头的数量,比如Ms开头的数量:

# 比如名字为Attalah, Miss. Malake,我们提取出目的是Miss,运用两次lambda
X_train['Name_Title'] = X_train['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])
X_test['Name_Title'] = X_test['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])
X_train.groupby('Name_Title')['Survived'].count()

输出:

Name_Title
Capt.        1
Col.         2
Don.         1
Dr.          4
Lady.        1
Major.       1
Master.     27
Miss.      126
Mlle.        1
Mme.         1
Mr.        365
Mrs.        87
Rev.         5
the          1
Name: Survived, dtype: int64

交叉数据:训练与测试集可视化对比差异:

# 条形图上的误差棒则表示各类的数值相对于条形图所显示的值的误差
# seaborn的barplot()利用矩阵条的高度反映数值变量的集中趋势,以及使用errorbar功能(差棒图)来估计变量之间的差值统计
fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))
sns.barplot('Name_Title', 'Survived', data=X_train.sort_values('Name_Title'), ax=axis1) 
sns.barplot('Name_Title', 'Survived', data=X_test.sort_values('Name_Title'), ax=axis2)

下面这个图显示的数据变量的集中趋势!

对比这个数据,得出如下结论:对于不同的名字开头,他的生存率不同,这里根据存活率平均程度高低依次下分。

特征工程处理:通过定义一个函数来对名字的存活性高低,进行详细排序:

def Name_Title_Code(x):
    if x == 'Mr.':
        return 1
    if (x=='Ms.') or (x=='Lady.') or (x == 'Mlle.') or (x =='Mme.') or (x == 'the.') or (x =='Sir.') or (x=='Major'):
        return 2
    if (x == 'Mrs.'):
        return 3
    if x == 'Miss.':
        return 4
    if x == 'Master.':
        return 5
    if x == 'Dr.':
        return 6
    return 7

对真正的训练集与测试集进行one-hot编码:

tannike_train['Name_Title'] = tannike_train['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])
tannike_test['Name_Title'] = tannike_test['Name'].apply(lambda x: x.split(',')[1]).apply(lambda x: x.split()[0])

tannike_train['Name_Title'] = tannike_train['Name_Title'].apply(Name_Title_Code)
tannike_test['Name_Title'] = tannike_test['Name_Title'].apply(Name_Title_Code)
tannike_train = pd.get_dummies(columns = ['Name_Title'], data = tannike_train)
tannike_test = pd.get_dummies(columns = ['Name_Title'], data = tannike_test)

船票编号与存活

获取船票第一个字母:

def Ticket_First_Let(x):
    return x[0]

交叉数据集:训练与测试集处理:

X_train['Ticket_First_Letter'] = X_train['Ticket'].apply(Ticket_First_Let)
X_test['Ticket_First_Letter'] = X_test['Ticket'].apply(Ticket_First_Let)

可视化船票编号与存活关系:

fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))
sns.barplot('Ticket_First_Letter', 'Survived', data=X_train.sort_values('Ticket_First_Letter'), ax=axis1) 
sns.barplot('Ticket_First_Letter', 'Survived', data=X_test.sort_values('Ticket_First_Letter'), ax=axis2)

同理,我们发现票的前面数字的编号对生存也是有影响!这里对不同的票编号封装成函数,进行处理!

def Ticket_First_Letter_Code(x):
    if (x == 'F'):
        return 1
    if x == '1' or x=='P' or x=='9':
        return 2
    if x == '2':
        return 3
    if x == 'C':
        return 4
    if x == 'S':
        return 5
    if x == '6':
        return 6
    if x == '7':
        return 7
    if x == 'A':
        return 8
    if x == 'W':
        return 9
    return 10

真实训练集与测试集处理:

one-hot编码处理:

tannike_train['Ticket_First_Letter'] = tannike_train['Ticket'].apply(Ticket_First_Let)
tannike_test['Ticket_First_Letter'] = tannike_test['Ticket'].apply(Ticket_First_Let)

tannike_train['Ticket_First_Letter'] = tannike_train['Ticket_First_Letter'].apply(Ticket_First_Letter_Code)
tannike_test['Ticket_First_Letter'] = tannike_test['Ticket_First_Letter'].apply(Ticket_First_Letter_Code)
tannike_train = pd.get_dummies(columns = ['Ticket_First_Letter'], data = tannike_train) 
tannike_test = pd.get_dummies(columns = ['Ticket_First_Letter'], data = tannike_test)

客舱号与生存

Cabin表示客舱号。首先我们来对数据缺失值,进行填充。将缺失值设为NA。

X_train['Cabin'] = X_train['Cabin'].fillna('NA')X_test['Cabin'] = X_test['Cabin'].fillna('NA')

其次定义一个方法:用于获取客舱号的第一个字母或数字。

def Cabin_First_Letter(x):
    if x == 'NA':
        return 'NA'
    return x[0]

将交叉验证集数据进行上述方法操作。

sns.barplot('Cabin_First_Letter', 'Survived', data=X_train.sort_values('Cabin_First_Letter'), ax=axis1) 
X_train['Cabin_First_Letter'] = X_train['Cabin'].apply(Cabin_First_Letter)X_test['Cabin_First_Letter'] = X_test['Cabin'].apply(Cabin_First_Letter) 

统计客舱号中第一个字母的出现次数:

X_train.groupby('Cabin_First_Letter')['Survived'].count()

结果:

Cabin_First_Letter
A      12
B      28
C      41
D      21
E      22
F       8
G       3
NA    488
Name: Survived, dtype: int64

接着,数据处理完了,我们是时候可视化数据,并确定该特征值与生存的关系!

fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))
sns.barplot('Cabin_First_Letter', 'Survived', data=X_train.sort_values('Cabin_First_Letter'), ax=axis1) 
sns.barplot('Cabin_First_Letter', 'Survived', data=X_test.sort_values('Cabin_First_Letter'), ax=axis2)

这里采用Seaborn的barplot绘制柱状图,图中的竖线代表误差棒!图显示的是每一个客舱号第一位数字或字母出现的集中分布趋势!

针对上述这个图,我们得出如下结论:

存活率排序:E>D>>B>C>A>.....

然后定义一个方法,进行封装,依次返回数值!

def Cabin_First_Letter_Code(x):    if x == 'E':        return 1    if x == 'D':        return 2    if x == 'B':        return 3    if x == 'C':        return 4    if x == 'A':        return 5    return 6

然后根据上述的交叉验证数据集的结果,应用到我们的测试集与训练集上。

tannike_train['Cabin'] = tannike_train['Cabin'].fillna('NA')tannike_test['Cabin'] = tannike_test['Cabin'].fillna('NA')

tannike_train['Cabin_First_Letter'] = tannike_train['Cabin'].apply(Cabin_First_Letter)tannike_test['Cabin_First_Letter'] = tannike_test['Cabin'].apply(Cabin_First_Letter) 

由于当前研究的这个数据为等级资料数据,我们这里用one-hot编码可有效的处理问题:

tannike_train['Cabin_First_Letter'] = tannike_train['Cabin_First_Letter'].apply(Cabin_First_Letter_Code)tannike_test['Cabin_First_Letter'] = tannike_test['Cabin_First_Letter'].apply(Cabin_First_Letter_Code)

tannike_train = pd.get_dummies(columns = ['Cabin_First_Letter'], data = tannike_train) tannike_test = pd.get_dummies(columns = ['Cabin_First_Letter'], data = tannike_test) 

登陆港口与生存

由于登录港口数据已经在前一节的缺失值处理掉了,这里便可以直接对交叉验证集数据可视化:

fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))
sns.barplot('Embarked', 'Survived', data=X_train.sort_values('Embarked'), ax=axis1) 
sns.barplot('Embarked', 'Survived', data=X_test.sort_values('Embarked'), ax=axis2)

然后我们对其进行生存排序:C>Q>S,由于只有三类,直接对训练集与测试集进行one-hot编码即可!

tannike_train = pd.get_dummies(tannike_train,columns = ['Embarked'])
tannike_test = pd.get_dummies(tannike_test,columns = ['Embarked'])

Pclass客舱等级(1/2/3等舱位)同Embarked操作,这个就不阐述了!

兄弟姐妹与生存

SibSp:兄弟姐妹数或配偶数,Parch:父母数或子女数,将其与Parch 合并处理!

X_train['Fam_Size'] = X_train['SibSp']  + X_train['Parch'] X_test['Fam_Size'] = X_test['SibSp']  + X_test['Parch'] 

可视化:

fig, (axis1,axis2) = plt.subplots(1,2,figsize=(15,5))sns.barplot('Fam_Size', 'Survived', data=X_train.sort_values('Parch'), ax=axis1) sns.barplot('Fam_Size', 'Survived', data=X_test.sort_values('Parch'), ax=axis2) 

特征处理:分为三类:Solo,Nuclear,Big,并对训练集与测试集进行one-hot编码!

import numpy as npdef Family_feature(train, test):    for i in [train, test]:        i['Fam_Size'] = np.where((i['SibSp']+i['Parch']) == 0 , 'Solo',                           np.where((i['SibSp']+i['Parch']) <= 3,'Nuclear', 'Big'))        del i['SibSp']        del i['Parch']    return train, test tannike_train, tannike_test  = Family_feature(tannike_train, tannike_test)

tannike_train = pd.get_dummies(tannike_train,columns = ['Fam_Size']) tannike_test =  pd.get_dummies(tannike_test,columns = ['Fam_Size'])

Fare同理操作!

特征选择

这里以全集特征训练!删除某一特征训练结果测试。

X_train_ = tannike_train.loc[X_train.index]X_test_ = tannike_train.loc[X_test.index]Y_train_ = label.loc[X_train.index]Y_test_ = label.loc[X_test.index]X_test_.head(3)X_test_ = X_test_[X_train_.columns]X_test_.head(3)tannike_test = tannike_test[tannike_train.columns]tannike_test.head()

模型选择

xgboost

这里选用xgboost进行测试,下面是选择的最优情况的代码,线下测试:0.8097014925373134,线上测试0.80621!

import xgboost as xgbfrom sklearn.preprocessing import LabelEncoder

gbm = xgb.XGBClassifier(max_depth=3, n_estimators=300, learning_rate=0.06,scoring='r3', cv=10, verbose=1, n_jobs=4,)gbm.fit(X_train_, Y_train_)gbm.score(X_test_,Y_test_)

随机森林

线下测试:0.8022388059701493,线上测试0.80621!

rf_ = RandomForestClassifier(random_state = 10, warm_start = True,                                   n_estimators = 900,                                  max_depth = 6,                                    max_features = 'sqrt',min_samples_leaf=1)rf_.fit(X_train_, Y_train_)rf_.score(X_test_,Y_test_)

结果上传

首先通过上述建立的模型进行预测,并输出到上传文件中,然后再上传 !

s = pd.read_csv('./gender_submission.csv')predictions = p.predict(tannike_test) # p为模型分类器submission = pd.DataFrame({"PassengerId": s.PassengerId, "Survived": predictions.astype(np.int32)})submission.to_csv("submission-pipe.csv", index=False)


总结

  • 泰坦尼克号数据大多属于等级资料,需要用one-hot编码;

  • 特征选择,加权的重要性;

  • 交叉验证集构建;

  • 多模型选择与模型融合;

  • 当前这个模型单一,特征提取没有进行逐一删选,后续。

参考:

  • https://www.kesci.com/home/project/5af18c294b7639369e6c289c

  • https://blog.csdn.net/wydyttxs/article/details/76695205

作者简介:光城,研一,个人公众号:guangcity,博客:http://light-city.me/, 个人研究方向:知识图谱,正致力于将机器学习运用到KG当中。

个人公众号简介:旨在推送一些个人学习python的一些历程,包括python爬虫,机器学习,LeetCode(每周更新2篇+)等。

微信改版了,

想快速看到CSDN的热乎文章,

赶快把CSDN公众号设为星标吧,

打开公众号,点击“设为星标”就可以啦!

征稿啦

CSDN 公众号秉持着「与千万技术人共成长」理念,不仅以「极客头条」、「畅言」栏目在第一时间以技术人的独特视角描述技术人关心的行业焦点事件,更有「技术头条」专栏,深度解读行业内的热门技术与场景应用,让所有的开发者紧跟技术潮流,保持警醒的技术嗅觉,对行业趋势、技术有更为全面的认知。

如果你有优质的文章,或是行业热点事件、技术趋势的真知灼见,或是深度的应用实践、场景方案等的新见解,欢迎联系 CSDN 投稿,联系方式:微信(guorui_1118,请备注投稿+姓名+公司职位),邮箱(guorui@csdn.net)。

推荐阅读:

  • 在操作系统、芯片领域跌倒的中国程序员,如何崛起?| 畅言

  • 如何不再被「单链表」面试题虐得男默女泪?

  • Cloud一分钟|茅台4.5亿入股云上贵州大数据,后者已接管苹果中国iCloud; 阿里云进入印度市场,增长速度远超当地平均水平

  • 以太坊2.0? 亲历3天的Devcon我看到了这样一个以太坊 | 见闻录

  • 大咖指路:机器学习人才这些方向最紧缺!

  • 唇语识别技术的开源教程,听不见声音我也能知道你说什么!

  • 学会这个,就拥有刷屏10亿用户的入场券!

如何用编程得出泰坦尼克号生还者的年龄段?相关推荐

  1. 'python program'.count('p')的值是_如何用Python分析泰坦尼克号生还率?

    原标题:如何用Python分析泰坦尼克号生还率? 1912年当时世界上最大的豪华客轮泰坦尼克号在处女航中撞上冰山沉没,船上船员及乘客共有2224人,只有710人生还.当灾难突然降临时,所有人的生死瞬间 ...

  2. 【数据分析师-数据分析项目案例二】泰坦尼克号生还者预测案例

    泰坦尼克号生还者预测案例 1 数据 1.1 数据下载 1.2 数据字段介绍 2 数据加载和基本的ETL 2.1 模块导入和数据加载 2.2 数据清洗 2.2.1 缺失值处理 2.2.2 分类数据独热编 ...

  3. 案例:使用seaborn分析泰坦尼克号生还者数据

    目录 一.数据来源(数据的导入) 二.主要分析的内容(定义问题) 泰坦尼克号乘客基本信息分布情况?? 乘客的信息与生还数据是否有关联?? 三.数据清洗 3.1 查看是否有缺失值 3.2 查看数据基本信 ...

  4. Python数据分析项目实例4:使用seaborn分析泰坦尼克号生还者数据

    泰坦尼克号生还者数据集下载(免费):https://download.csdn.net/download/weixin_44940488/20814899 使用的分析软件:jupyter notebo ...

  5. python编程爱因斯坦的问题_教你如何用编程解决爱因斯坦的数学问题

    曾经爱因斯坦出了一道这样的数学题:有一条长阶梯,若每步跨2阶,则最后剩1阶,若每步跨3阶,则最后剩2阶,若每步跨5阶,则最后剩4阶,若每步跨6阶则最后剩5阶.只有每次跨7阶,最后才正好一阶不剩.请问在 ...

  6. Enterprise Library 2.0 技巧(4):如何用编程的方法来配置Logging Application Block

    在本系列的技巧(1)和技巧(2)中分别介绍了使用外部配置文件,使用数据库记录配置信息两种方法,不知道大家有没有想过不使用任何配置文件,也不使用数据库而直接用编程的方法来实现呢?本文将会展示如何使用编程 ...

  7. 语言用pad流程图求和例题_易编玩初级课解析:如何用编程玩转流程图?

    纵观历年高考题,每年的高考试卷中总会出现这样的试题:这种图就是数学中常见的流程图,通过流程图可以明确的表示某个算法,或者程序从开始到结束的动态过程.对于孩子的程序性思维能力.语言概括提炼能力和统筹能力 ...

  8. 程序员如何用编程套路追到女朋友的?

    最近身边的程序员总在抱怨说同学朋友都结婚了,自己连对象在哪都还不知道.程序员找女朋友真的很难吗?在本文作者看来的话,程序员找女朋友无非就是写程序的那一套:编辑.预处理.编译.连接.运行这五个阶段.下面 ...

  9. 如何用编程 get 百万年终奖?

    2018 年 CSDN 软件开发者大调查活动开始了!自 2004 年开始,我们通过对开发人员.开发技术以及开发工具.平台的状况和发展趋势等进行深入的调研,为开发者呈现了一幅幅真实的中国开发者画像.十四 ...

最新文章

  1. Saltstack自动化环境部署
  2. 从零开始入门 K8s | 应用存储和持久化数据卷:存储快照与拓扑调度
  3. 在IDEA连接MySql数据库时报错: [08001] CLIENT_PLUGIN_AUTH is required com.mysql.cj.exceptions.
  4. onmousemove和onmouseout事件的调用,和js使用双引号、单引号的时候应该注意的问题...
  5. WIN 10 安装 Hadoop 2.7.7 + Spark 2.4.7 记录
  6. R7-7 寻找大富翁 (25 分)
  7. 基于JAVA+SpringMVC+MYSQL的博客系统
  8. 【分享】具有“魔性”的通用软件开发框架
  9. C# : 操作Word文件的API - (将C# source中的xml注释转换成word文档)
  10. 一文读懂什么是绿色工厂以及绿色工厂建设细解
  11. 网站服务器停止运行,服务器已停止响应是怎么回事
  12. 网站优化nofollow标签的作用,如何加nofollow标签
  13. rails erb_您需要知道Rails中的erb以及如何掌握它
  14. 二进制安装Kubernetes(k8s) v1.25.0 IPv4/IPv6双栈
  15. Reference 、ReferenceQueue 详解
  16. MATLAB:巴特沃斯低通滤波器过滤信号
  17. 石头说:一道黑色的光,飞速地向我走来。
  18. MySQL-7.0.27-win64下载和配置(Windows版本,免安装)
  19. 朋友问我移居香港后悔没?这回答够真实!
  20. 关于专利技术交底书分类的真相

热门文章

  1. rust笔记4 slice类型
  2. 中国双接口芯片卡市场趋势报告、技术动态创新及市场预测
  3. 在哪能看全国计算机考试试题,2016年全国计算机一级考试试题汇集
  4. php中左右去电空格,PHP中ltrim与rtrim去除左右空格及特殊字符实例
  5. 新年第一份“欧气”,“中国开发者大调查”第五批中奖名单出炉啦
  6. 从 Angular 移植到 React,代码量减少了 20%
  7. 微软 Azure 再下一城:收购 Kinvolk,改进开源 Linux
  8. 华为一则面试题登上热搜;大众点评会员因点赞过多被处罚;Linux Kernel 5.12 发布|极客头条...
  9. 爷青结?诺基亚贝尔实验室官宣转让Plan 9版权!
  10. 微软高层人士变动!张祺晋升为微软公司全球资深副总裁