Task02:数据读取与数据分析

学习目标

  • 分析赛题数据的分布规律
  • 通过这次学习定义一个自己的句子分析类,可以用来循环使用,进而分析所有相似的data

数据大小:

训练集20w条样本,测试集A包括5w条样本,测试集B包括5w条样本。

数据读取:

import pandas as pd
data_root={
    "train_path":"../data/train_set.csv",
    "test_path":"../data/test_a.csv",
    "sub_path":"../data/test_a_sample_submit.csv"
}
train=pd.read_csv(data_root["train_path"], sep='\t')
test=pd.read_csv(data_root["test_path"])
sub=pd.read_csv(data_root["sub_path"])

训练集样式:第一列为新闻的类别,第二列为新闻的字符。

label text
6 57 44 66 56 2 3 3 37 5 41 9 55

数据分析:

1. 句子长度分析

注意到文本是长这样的:

train.text.values[199998]
# output 
'6405 3203 6644 983 794 1913 1678 5736 1397 1913 5221 1722 2410'

所以要先split(" ") 然后len

train["text_len"]=train.text.apply(lambda x:len(x.split(" ")))

看一眼统计分析结果

train_df['text_len'].describe()
# output
count    200000.000000
mean        907.207110
std         996.029036
min           2.000000
25%         374.000000
50%         676.000000
75%        1131.000000
max       57921.000000
Name: text_len, dtype: float64

我们用hist作图

train.text_len.hist(bins=100)
plt.xlabel('Text char count')
plt.title("Histogram of char count");

2. 新闻类别分布

df_label=train.groupby("label").agg({"text":["count"],"text_len":["max","min","mean"]})
label count len_max len_min len_mean
0 38918 18587 12 878.717663
1 36945 57921 9 870.363676
2 31425 41894 2 1014.429562
3 22133 10817 17 784.774726
4 15016 14928 25 649.705647
5 12232 15839 27 1116.054938
6 9985 25728 16 1249.114071
7 8841 14469 11 1157.883271
8 7847 15271 7 712.401172
9 5878 23866 17 833.627084
10 4920 20622 14 911.138008
11 3131 5729 21 608.889812
12 1821 8737 25 1194.969248
13 908 6399 26 735.325991
train["label"].value_counts().plot(kind="bar")
plt.title('News class count')
plt.xlabel("category");

标签的对应的关系如下:{'科技': 0, '股票': 1, '体育': 2, '娱乐': 3, '时政': 4, '社会': 5, '教育': 6, '财经': 7, '家居': 8, '游戏': 9, '房产': 10, '时尚': 11, '彩票': 12, '星座': 13}
从统计结果可以看出,赛题的数据集类别分布存在较为不均匀的情况。在训练集中科技类新闻最多,其次是股票类新闻,最少的新闻是星座新闻。

3. 字符分布统计

注意到每一行都是空格分割的字符,所以我们将不同行也用空格相连,然后对所有项进行split(" ")得到所有字符的list,然后统计个数

from collections import Counter
all_lines=" ".join(list(train["text"]))
word_count=Counter(all_lines.split(" "))

使用Counter 类可以轻松得到出现次数最多和最少的单词(当然最少的单词很多)

  • 出现次数最多的10个单词,其中编号3750的字出现的次数最多。
word_count.most_common(10)
#output
[('3750', 7482224),
 ('648', 4924890),
 ('900', 3262544),
 ('3370', 2020958),
 ('6122', 1602363),
 ('4464', 1544962),
 ('7399', 1455864),
 ('4939', 1387951),
 ('3659', 1251253),
 ('4811', 1159401)]
  • 出现次数最少的5个单词,都是1次。
word_count.most_common()[-5:]
#output
[('155', 1), ('1415', 1), ('1015', 1), ('4468', 1), ('3133', 1)]
  • 统计字典长度
len(word_count)
#output
6869

从统计结果中可以看出,在训练集中总共包括6869个字。

  • 统计了不同字符在句子中出现的次数

这里还可以根据字在每个句子的出现情况,反推出标点符号。
统计了不同字符在句子中出现的次数,只需要对每一句的字符进行去重就可以了,所以只需要对上面的代码进行轻微改动即可。

train['text_unique'] = train['text'].apply(lambda x: ' '.join(list(set(x.split(' ')))))
all_lines = ' '.join(list(train['text_unique']))
word_count = Counter(all_lines.split(" "))

其中字符3750,字符900和字符648在20w新闻的覆盖率超过95%,很有可能是标点符号。

for k,v in word_count.most_common(10):
    print("字符编号为 {:>4} 在所有句子中的比例为: {:.2%}".format(k,v/200000))
# output 
字符编号为 3750 在所有句子中的比例为: 99.00%
字符编号为  900 在所有句子中的比例为: 98.83%
字符编号为  648 在所有句子中的比例为: 95.99%
字符编号为 2465 在所有句子中的比例为: 88.66%
字符编号为 6122 在所有句子中的比例为: 88.27%
字符编号为 7399 在所有句子中的比例为: 88.12%
字符编号为 4811 在所有句子中的比例为: 84.69%
字符编号为 4464 在所有句子中的比例为: 83.58%
字符编号为 1699 在所有句子中的比例为: 82.43%
字符编号为 3659 在所有句子中的比例为: 81.59%

数据分析结论:

通过上述分析我们可以得出以下结论:

  1. 赛题中每个新闻包含的字符个数平均为1000个,还有一些新闻字符较长;
  2. 赛题中新闻类别分布不均匀,科技类新闻样本量接近4w,星座类新闻样本量不到1k;
  3. 赛题总共包括7000-8000个字符(包括测试集);

通过数据分析,我们还可以得出以下结论:

  1. 每个新闻平均字符个数较多,可能需要截断;
  2. 由于类别不均衡,会严重影响模型的精度;

本章作业:

1. 假设字符3750,字符900和字符648是句子的标点符号,请分析赛题每篇新闻平均由多少个句子构成?
for symbol in ["3750","900","648"]:
    col_name="sentence_count_by_{}".format(symbol)
    train[col_name]=train.text.apply(lambda x:len(x.split(symbol)))

做一些数据分析和可视化展示:

for symbol in ["3750","900","648"]:
    col_name="sentence_count_by_{}".format(symbol)
    print(train[col_name].describe())
    train[col_name].hist(bins=100)
    plt.title(symbol)
    plt.show()
#output
count    200000.00000
mean         38.41112
std          40.87367
min           1.00000
25%          14.00000
50%          27.00000
75%          49.00000
max        1960.00000
Name: sentence_count_by_3750, dtype: float64

#output
count    200000.000000
mean         17.335255
std          18.047099
min           1.000000
25%           7.000000
50%          12.000000
75%          22.000000
max         735.000000
Name: sentence_count_by_900, dtype: float64

#output
count    200000.000000
mean         27.055995
std          31.958496
min           1.000000
25%           8.000000
50%          18.000000
75%          35.000000
max        1394.000000
Name: sentence_count_by_648, dtype: float64

其实还有些奇怪的,因为这些字符都不在整篇文章的结尾,那么这种分句子的方式就会留下一段不完整的东西,所以还要做一个句尾字符分析。

  • 句尾字符分析:
    对于最后一个字符,是否截断,给出假设:
    如果没有被截断,那么末尾那个数字应该会有很大一部分是一样的。
train["last_word"]=train.text.apply(lambda x: x.split(" ")[-1])
last_word_count=Counter(train["last_word"])
last_word_count.most_common(10)
# output
[('900', 85040),
 ('2662', 39273),
 ('885', 14473),
 ('1635', 7379),
 ('2465', 7076),
 ('57', 3284),
 ('3231', 2758),
 ('1633', 2706),
 ('3568', 1504),
 ('2265', 1389)]

检查上述三个字符是在文章尾巴的次数:由结果可以看出,我们更倾向于900是句号,并且大多数文章没有被截断。

for symbol in ["3750","900","648"]:
    print(last_word_count[symbol])
# output
17
85040
17
2. 统计每类新闻中出现次数对多的字符

用一个字典记录每一类新闻的Counter字典就好了。

def get_word_group_count():
    word_group_count={}
    for name,group in train[["label","text"]].groupby("label"):
        all_lines=" ".join(list(group.text))
        word_count=Counter(all_lines.split(" "))
        word_group_count[name]=word_count
    return word_group_count

word_group_count=get_word_group_count()
for i in range(14):
    print("标签为第{:>2d}组,最多的五个单词为 {} ".format(i,word_group_count[i].most_common(5)))
标签为第 0组,最多的五个单词为 [('3750', 1267331), ('648', 967653), ('900', 577742), ('3370', 503768), ('4464', 307431)] 
标签为第 1组,最多的五个单词为 [('3750', 1200686), ('648', 714152), ('3370', 626708), ('900', 542884), ('4464', 445525)] 
标签为第 2组,最多的五个单词为 [('3750', 1458331), ('648', 974639), ('900', 618294), ('7399', 351894), ('6122', 343850)] 
标签为第 3组,最多的五个单词为 [('3750', 774668), ('648', 494477), ('900', 298663), ('6122', 187933), ('4939', 173606)] 
标签为第 4组,最多的五个单词为 [('3750', 360839), ('648', 231863), ('900', 190842), ('4411', 120442), ('7399', 86190)] 
标签为第 5组,最多的五个单词为 [('3750', 715740), ('648', 329051), ('900', 305241), ('6122', 159125), ('5598', 136713)] 
标签为第 6组,最多的五个单词为 [('3750', 469540), ('648', 345372), ('900', 222488), ('6248', 193757), ('2555', 175234)] 
标签为第 7组,最多的五个单词为 [('3750', 428638), ('648', 262220), ('900', 184131), ('3370', 159156), ('5296', 132136)] 
标签为第 8组,最多的五个单词为 [('3750', 242367), ('648', 202399), ('900', 92207), ('6122', 57345), ('4939', 56147)] 
标签为第 9组,最多的五个单词为 [('3750', 178783), ('648', 157291), ('900', 70680), ('7328', 46477), ('6122', 43411)] 
标签为第10组,最多的五个单词为 [('3750', 180259), ('648', 114512), ('900', 75185), ('3370', 67780), ('2465', 45163)] 
标签为第11组,最多的五个单词为 [('3750', 83834), ('648', 67353), ('900', 37240), ('4939', 18591), ('6122', 18438)] 
标签为第12组,最多的五个单词为 [('3750', 87412), ('4464', 51426), ('3370', 45815), ('648', 37041), ('2465', 36610)] 
标签为第13组,最多的五个单词为 [('3750', 33796), ('648', 26867), ('900', 11263), ('4939', 9651), ('669', 8925)] 
3. 作业中的数据分析结论:

1.由句尾分析可以看出,我们更倾向于900是句号,并且大多数文章没有被截断。

  1. 标签第12组,“4464” 突然变多,可见彩票类新闻,有大量这个字符,暂时没有猜想,在每类新闻中最多的单词一般都是标点符号。

定义一个句子分析类:

将上面代码整理之后,重新构建一个句子分析的类,这样在分析测试集时,可以重复调用,更详细的参考信息可以参考另外一个我的博客

代码已经上传至Github

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from collections import Counter
class SentenceAnalysis():
    def __init__(self,data_path,n_classes=None,with_label=True):
        self.data_path=data_path
        self.with_label=with_label #测试集无标签导入
        self.n_classes=n_classes
        self.load_dataset()
    
    @property
    def data(self):
        if self.with_label==True:
            return self.X,self.Y
        else:
            return self.X
    
    def load_dataset(self):
        if self.with_label:
            train=pd.read_csv(self.data_path, sep='\t')
            self.X=train[[col for col in train.columns if col!="label"]]
            self.Y=train["label"]
        else:
            test=pd.read_csv(self.data_path)
            self.X=test
            self.Y=None
    
    def __len__(self):
        return self.X.shape[0]

    def __getitem__(self, index):
        '''Generate one  of data'''
        x = self.X.iloc[int(index)]
        if self.with_label == True:
            y=self.Y[int(index)]
            # y=one_hot(y,self.n_classes)
            return x,y
        else:
            return x
    
    def passage_length_ana(self,show_describe=True,show_hist=False):
        """
        句子长度分析
        """
        df=self.X.copy()
        df["text_len"]=df.text.apply(lambda x:len(x.split(" ")))
        if show_describe:
            print(df["text_len"].describe())
        if show_hist:
            train.text_len.hist(bins=100)
            plt.xlabel('Text char count')
            plt.title("Histogram of char count");
        return df["text_len"]

    def show_hist(self,data,bins=100,title="Not define.",xlabel="no xlabel."):
        data.hist(bins=bins)
        plt.xlabel(xlabel)
        plt.title(title);
        return 

    def label_distribution(self,show_bar=True,title='class count',xlabel="category"):
        """
        label分布的分析
        """
        if not self.with_label:
            print("没有可用的标签!")
            return
        df=self.X.copy()
        df["label"]=self.Y.values
        df_label=df.groupby("label").agg({"text":["count"]})
        if show_bar:
            df["label"].value_counts().plot(kind="bar")
            plt.title(title)
            plt.xlabel(xlabel);
        return df_label
    

    def word_distribution(self,show_most=1,show_least=1):
        """
        字符分布
        """
        show_most,show_least=int(show_most),int(show_least)
        df=self.X.copy()
        all_lines=" ".join(list(df["text"]))
        word_count=Counter(all_lines.split(" "))
        if show_most>0:
            print("最多的{}个字符:".format(show_most))
            print(word_count.most_common(int(show_most)))
        if show_least>0:
            print("最少的{}个字符:".format(show_least))
            print(word_count.most_common()[-int(show_least):])
        print("所有文档中拥有字符数: {}".format(len(word_count)))
        return word_count
    
    def word_in_sentece_distribution(self,show_most=1,show_least=0):
        """
        统计了不同字符在句子中出现的次数
        """
        show_most,show_least=int(show_most),int(show_least)
        df=self.X.copy()
        df['text_unique'] = df['text'].apply(lambda x: ' '.join(list(set(x.split(' ')))))
        all_lines = ' '.join(list(df['text_unique']))
        word_count = Counter(all_lines.split(" "))
        if show_most>0:
            print("最多的{}个字符:".format(show_most))
            for k,v in word_count.most_common(show_most):
                print("字符编号为 {:>4} 在所有句子中的比例为: {:.2%}".format(k,v/self.X.shape[0]))
        if show_least>0:
            print("最少的{}个字符:".format(show_least))
            for k,v in word_count.most_common()[-int(show_least):]:
                print("字符编号为 {:>4} 在所有句子中的比例为: {:.2%}".format(k,v/self.X.shape[0]))
        return word_count
    
    def word_groupbylabel_count(self,show_most=1):
        """
        统计每类新闻中出现次数最多的字符
        """
        show_most=int(show_most)
        if not self.with_label:
            print("没有可用的标签!")
            return
        df=self.X.copy()
        df["label"]=self.Y.values
        word_group_count={}
        for name,group in df[["label","text"]].groupby("label"):
            all_lines=" ".join(list(group.text))
            word_count=Counter(all_lines.split(" "))
            word_group_count[name]=word_count
        if show_most>0:
            if not self.n_classes:
                self.n_classes=self.Y.nunique()
            for i in range(self.n_classes):
                print("标签为第{:>2d}组,最多的{}个单词为 {} ".format(i,show_most,word_group_count[i].most_common(show_most)))
        return word_group_count
    

    def last_word_ana(self,show_most=1,show_least=1):
        """
        句尾分析
        """
        show_most,show_least=int(show_most),int(show_least)
        df=self.X.copy()
        df["last_word"]=df.text.apply(lambda x: x.split(" ")[-1])
        last_word_count=Counter(df["last_word"])
        if show_most>0:
            print("最多的{}个字符:".format(show_most))
            print(last_word_count.most_common(int(show_most)))
        if show_least>0:
            print("最少的{}个字符:".format(show_least))
            print(last_word_count.most_common()[-int(show_least):])
        print("所有文档中不同的最后一个字符数: {}".format(len(last_word_count)))
        return last_word_count

功能展示:

  • 对于训练集:
train_path="../data/train_set.csv"
sentence_train=SentenceAnalysis(train_path,n_classes=14,with_label=True)
# __getitem__
sentence_train[1]
# __len__
len(sentence_train)
# data
train_X,train_y=sentence_train.data
# 文章长度分析
df_length=sentence_train.passage_length_ana()
# 辅助的作图
sentence_train.show_hist(df_length,100,'Text char count',"Histogram of char count")
# 新闻类别分布
df_label=sentence_train.label_distribution()
# 字符个数分布
word_dict=sentence_train.word_distribution(5,5)
# 不同字符在句子中出现的次数
word_in_sentece_dict=sentence_train.word_in_sentece_distribution(5)
# 统计每类标签中出现次数最多的字符
word_group_count=sentence_train.word_groupbylabel_count(5)
# 句尾分析
last_word_count=sentence_train.last_word_ana(2,3)
  • 对于测试集:
test_path="../data/test_a.csv"
sentence_test=SentenceAnalysis(test_path,n_classes=14,with_label=False)
# 功能展示
# __getitem__
sentence_test[1]
# __len__
len(sentence_test)
# data
sentence_test.data
# 文章长度分析
df_length=sentence_test.passage_length_ana()
# 辅助的作图
sentence_test.show_hist(df_length,100,'Text char count',"Histogram of char count")
# 新闻类别分布(没有标签,给出提示不可做分析。)
sentence_test.label_distribution()
# 字符个数分布
word_dict=sentence_test.word_distribution(5)
# 不同字符在句子中出现的次数
word_in_sentece_dict=sentence_test.word_in_sentece_distribution(2,3)
# 统计每类标签中出现次数最多的字符(没有标签,给出提示不可做分析。)
word_group_count=sentence_test.word_groupbylabel_count(5)
# 句尾分析
last_word_count=sentence_test.last_word_ana(2,3)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356