第2章 探索資料
本章會介紹一些技術,幫你更好地理解資料,以及探索特徵之間的關係。你將學習以下主題:
·生成描述性的統計資料
·探索特徵之間的相關性
·視覺化特徵之間的相互作用
·生成直方圖
·建立多變數的圖表
·資料取樣
·將資料集拆分成訓練集、交叉驗證集和測試集
2.1導論
接下來的技巧會使用Python與D3.js來建立對資料的理解。我們會分析變數的分佈,捋清特徵之間的關係,並將其相互作用視覺化。你會學到生成直方圖及在二維圖表中表示三維資料的方法。最後,你會學習給樣本分層,並將資料集拆分成測試集與訓練集。
2.2生成描述性的統計資料
要完全理解任何隨機變數的分佈,我們需要知道其平均數與標準差、最小值與最大值、中位數、四分位數、偏度和峰度。
獨立安裝 pandas
pip install pandas
生成描述性資料,簡單示例1-使用padas
pandas有個很管用的.describe()方法,它替我們做了大部分的工作。這個方法能生成我們想要的大部分描述變數;輸出看起來是這樣的(為清晰做了相應簡化):
1 import pandas as pd 2 3 # name of the file to read from 4 r_filenameCSV = '../../Data/Chapter02/' + \ 5 'realEstate_trans_full.csv' 6 7 # name of the output file 8 w_filenameCSV = '../../Data/Chapter02/' + \ 9 'realEstate_descriptives.csv' 10 11 # read the data 12 csv_read = pd.read_csv(r_filenameCSV) 13 14 # calculate the descriptives: count, mean, std, 15 # min, 25%, 50%, 75%, max 16 # for a subset of columns 17 # 對某些列計算描述變數:總數,平均數、標準差、最小值 18 #25%數、50%數、75%數、最大值 19 csv_desc = csv_read[ 20 [ 21 'beds','baths','sq__ft','price','s_price_mean', 22 'n_price_mean','s_sq__ft','n_sq__ft','b_price', 23 'p_price','d_Condo','d_Multi-Family', 24 'd_Residential','d_Unkown' 25 ] 26 ].describe().transpose() 27 28 # and add skewness(偏度),mode(眾數) and kurtosis(峰度) 29 csv_desc['skew'] = csv_read.skew(numeric_only=True) 30 csv_desc['mode'] = \ 31 csv_read.mode(numeric_only=True).transpose() 32 csv_desc['kurtosis'] = csv_read.kurt(numeric_only=True) 33 34 print(csv_desc)
DataFrame物件的索引標明瞭描述性統計資料的名字,每一列代表我們資料集中一個特定的變數。不過,我們還缺偏度、峰度和眾數。為了更方便地加入csv_desc變數,我們使用.transpose()移項了.describe()方法的輸出結果,使得變數放在索引裡,每一列代表描述性的變數。
簡單示例2-使用SciPy和NumPy
獨立安裝 SciPy
pip install SciPy
.genfromtxt(...)方法以檔名作為第一個(也是唯一必需的)引數。本例中分隔符是',',也可以是\t。names引數指定為True,意味著變數名存於第一行。最後,usecols引數指定檔案中哪些列要存進csv_read物件。
最終可以計算出要求的資料:
import scipy.stats as st import numpy as np # name of the file to read from r_filenameCSV = '../../Data/Chapter02/' + \ 'realEstate_trans_full.csv' # read the data csv_read = np.genfromtxt( r_filenameCSV, delimiter=',', names=True, # only numeric columns usecols=[4,5,6,8,11,12,13,14,15,16,17,18,19,20] ) # calculate the descriptives desc = st.describe([list(item) for item in csv_read]) # and print out to the screen print(desc)
.genfromtxt(...)方法建立的資料是一系列元組。.describe(...)方法只接受列表形式的資料,所以得先(使用列表表示式)將每個元組轉換成列表。
http://docs.scipy.org/doc/scipy/reference/stats.html#statistical-functions
2.3探索特徵之間的相關性
兩個變數之間的相關係數用來衡量它們之間的關係。
係數為1,我們可以說這兩個變數完全相關;係數為-1,我們可以說第二個變數與第一個變數完全負相關;係數0意味著兩者之間不存在可度量的關係。
這裡要強調一個基礎事實:不能因為兩個變數是相關的,就說兩者之間存在因果關係。要了解更多,可訪問https://web.cn.edu/kwheeler/logic_causation.html。
我們將測算公寓的臥室數目、浴室數目、樓板面積與價格之間的相關性。
原理:pandas可用於計算三種相關度:皮爾遜積矩相關係數、肯達爾等級相關係數和斯皮爾曼等級相關係數。後兩者對於非正態分佈的隨機變數並不是很敏感。
我們計算這三種相關係數,並且將結果存在csv_corr變數中。
import pandas as pd # name of the file to read from r_filenameCSV = '../../Data/Chapter02/' + \ 'realEstate_trans_full.csv' # name of the output file w_filenameCSV = '../../Data/Chapter02/' + \ 'realEstate_corellations.csv' # read the data and select only 4 variables csv_read = pd.read_csv(r_filenameCSV) csv_read = csv_read[['beds','baths','sq__ft','price']] # calculate the correlations #皮爾遜積矩相關係數、肯達爾等級相關係數、斯皮爾曼級相關係數 coefficients = ['pearson', 'kendall', 'spearman'] csv_corr = {} for coefficient in coefficients: csv_corr[coefficient] = csv_read \ .corr(method=coefficient) \ .transpose() # output to a file with open(w_filenameCSV,'w') as write_csv: for corr in csv_corr: write_csv.write(corr + '\n') write_csv.write(csv_corr[corr].to_csv(sep=',')) write_csv.write('\n') */
參考,也可以使用NumPy計算皮爾遜相關係數:http://docs.scipy.org/doc/numpy/reference/generated/numpy.corrcoef.html。
2.4視覺化特徵之間的相互作用
D3.js是Mike Bostock用以視覺化資料的一個強大框架。它能幫你使用HTML、SVG和CSS來運算元據。本技巧將探索房屋的面積與價格之間是否存在聯絡。
程式碼分兩部分:準備資料(pandas和SQLAlchemy)和呈現資料(HTML與D3.js)。
上一章1.7已經將csv資料寫入mysql,程式碼如下:
1 import pandas as pd 2 import sqlalchemy as sa 3 # from sqlalchemy.ext.declarative import declarative_base 4 # from sqlalchemy import create_engine 5 6 # name of the CSV file to read from and SQLite database 7 r_filenameCSV = '../../Data/Chapter01/realEstate_trans.csv' 8 rw_filenameSQLite = '../../Data/Chapter01/realEstate_trans__Result.db' 9 10 11 12 # create the connection to the database 13 engine = sa.create_engine("mysql+pymysql://root:downmoon@localhost:3306/test?charset=utf8") 14 15 16 #=============================================================================== 17 # Base = declarative_base() 18 # engine = sa.create_engine("mysql+pymysql://root:downmoon@localhost:3306/test?charset=utf8") 19 # 20 # Base.metadata.reflect(engine) 21 # tables = Base.metadata.tables 22 # 23 # print(tables) 24 # # 獲取本地test資料庫中的 real_estates 表 25 # real_estates = tables["real_estate"] 26 # 27 # # 檢視engine包含的表名 28 # print(engine.table_names()) 29 #=============================================================================== 30 31 32 33 # read the data 34 csv_read = pd.read_csv(r_filenameCSV) 35 36 # transform sale_date to a datetime object 37 csv_read['sale_date'] = pd.to_datetime(csv_read['sale_date']) 38 39 # store the data in the database 40 csv_read.to_sql('real_estate', engine, if_exists='replace') 41 42 # print the top 10 rows from the database 43 query = 'SELECT * FROM real_estate LIMIT 5' 44 top5 = pd.read_sql_query(query, engine) 45 print(top5)
使用SQL Alchemy從mySQL資料庫中提取資料。下面給出查詢的例子(data_interaction.py檔案),取出的資料儲存在/Data/Chapter02/realEstate_d3.csv檔案中。
1 import pandas as pd 2 import sqlalchemy as sa 3 4 # names of the files to output the samples 5 w_filenameD3js = '../../Data/Chapter02/realEstate_d3.csv' 6 7 # database credentials 8 usr = 'root' 9 pswd = 'downmoon' 10 dbname='test' 11 12 # create the connection to the database 13 engine = sa.create_engine( 14 'mysql+pymysql://{0}:{1}@localhost:3306/{2}?charset=utf8' \ 15 .format(usr, pswd,dbname) 16 ) 17 18 # read prices from the database 19 query = '''SELECT sq__ft, 20 price / 1000 AS price 21 FROM real_estate 22 WHERE sq__ft > 0 23 AND beds BETWEEN 2 AND 4''' 24 data = pd.read_sql_query(query, engine) 25 26 # output the samples to files 27 with open(w_filenameD3js,'w',newline='') as write_csv: 28 write_csv.write(data.to_csv(sep=',', index=False))
使用這個框架前得先匯入。這裡提供用D3.js建立散佈圖的虛擬碼。下一節我們會一步步解釋程式碼:
原理:
首先,往HTML的DOM(Document Object Model)結構追加一個SVG(Scalable Vector Graphics)物件:
var width = 800; var height = 600; var spacing = 60; // Append an SVG object to the HTML body var chart = d3.select('body') .append('svg') .attr('width', width + spacing) .attr('height', height + spacing) ;
使用D3.js從DOM中取出body物件,加上一個SVG物件,指定屬性、寬和高。SVG物件加到了DOM上,現在該讀取資料了。可由下面的程式碼完成:
// Read in the dataset (from CSV on the server) d3.csv('http://localhost:8080/examples/realEstate_d3.csv', function(d) { draw(d) });
使用D3.js提供的方法讀入CSV檔案。.csv(...)方法的第一個引數指定了資料集;本例讀取的是tomcat上的CSV檔案(realEstate_d3.csv)。
第二個引數是一個回撥函式,這個函式將呼叫draw(...)方法處理資料。
D3.js不能直接讀取本地檔案(儲存在你的電腦上的檔案)。你需要配置一個Web伺服器(Apache或Node.js都可以)。如果是Apache,你得把檔案放在伺服器上,如果是Node.js,你可以讀取資料然後傳給D3.js。
draw(...)函式首先找出價格和樓板面積的最大值;該資料會用於定義圖表座標軸的範圍:
function draw(dataset){ // Find the maximum price and area var limit_max = { 'price': d3.max(dataset, function(d) { return parseInt(d['price']); }), 'sq__ft': d3.max(dataset, function(d) { return parseInt(d['sq__ft']); }) };
我們使用D3.js的.max(...)方法。這個方法返回傳入陣列的最大元素。作為可選項,你可以指定一個accessor函式,訪問資料集中的資料,根據需要進行處理。我們的匿名函式對資料集中的每一個元素都返回作為整數解析的結果(資料是作為字串讀入的)。
下面定義範圍:
// Define the scales var scale_x = d3.scale.linear() .domain([0, limit_max['price']]) .range([spacing, width - spacing * 2]); var scale_y = d3.scale.linear() .domain([0, limit_max['sq__ft']]) .range([height - spacing, spacing]);
D3.js的scale.linear()方法用傳入的引數建立了線性間隔的標度。domain指定了資料集的範圍。我們的價格從0到$884000(limit_max['price']),樓板面積從0到5822(limit_max['sq__ft'])。range(...)的引數指定了domain如何轉換為SVG視窗的大小。對於scale_x,有這麼個關係:如果price=0,圖表中的點要置於左起60畫素處;如果價格是$884000,點要置於左起600(width)-60(spacing)*2=480畫素處。
下面定義座標軸:
//Define the axes var axis_x = d3.svg.axis() .scale(scale_x) .orient('bottom') .ticks(5); var axis_y = d3.svg.axis() .scale(scale_y) .orient('left') .ticks(5);
座標軸要有標度,這也是我們首先傳的。對於axis_x,我們希望它處於底部,所以是.orient('bottom'),axis_y要處於圖表的左邊(.orient('left'))。.ticks(5)指定了座標軸上要顯示多少個刻度標識;D3.js自動選擇最佳間隔。
你可以使用.tickValues(...)取代.ticks(...),自行指定刻度值。
現在準備好在圖表上繪出點了:
// Draw dots on the chart chart.selectAll('circle') .data(dataset) .enter() .append('circle') .attr('cx', function(d) { return scale_x(parseInt(d['price'])); }) .attr('cy', function(d) { return scale_y(parseInt(d['sq__ft'])); }) .attr('r', 3) ;
首先選出圖表上所有的圓圈;因為還沒有圓圈,所以這個命令返回一個空陣列。.data(dataset).enter()鍊形成了一個for迴圈,對於資料集中的每個元素,往我們的圖表上加一個點,即.append('circle')。每個圓圈需要三個引數:cx(水平位置)、cy(垂直位置)和r(半徑)。.attr(...)指定了這些引數。看程式碼就知道,我們的匿名函式返回了轉換後的價格和麵積。
繪出點之後,我們可以畫座標軸了:
// Append X axis to chart chart.append('g') .attr('class', 'axis') .attr('transform', 'translate(0,' + (height - spacing) + ')') .call(axis_x); // Append Y axis to chart chart.append('g') .attr('class', 'axis') .attr('transform', 'translate(' + spacing + ',0)') .call(axis_y);
D3.js的文件中指出,要加上座標軸,必須先附加一個g元素。然後指定g元素的class,使其黑且細(否則,座標軸和刻度線都會很粗——參見HTML檔案中的CSS部分)。translation屬性將座標軸從圖表的頂部挪到底部,第一個引數指定了沿著橫軸的動作,第二個引數指定了縱向的轉換。最後呼叫axis_x。儘管看上去很奇怪——為啥要呼叫一個變數?——原因很簡單:axis_x不是陣列,而是能生成很多SVG元素的函式。呼叫這個函式,可以將這些元素加到g元素上。
最後,加上標籤,以便人們瞭解座標軸代表的意義:
// Append axis labels chart.append('text') .attr("transform", "translate(" + (width / 2) + " ," + (height - spacing / 3) + ")") .style('text-anchor', 'middle') .text('Price $ (,000)'); chart.append("text") .attr("transform", "rotate(-90)") .attr("y", 14) .attr("x",0 - (height / 2)) .style("text-anchor", "middle") .text("Floor area sq. ft.");
我們附加了一個文字框,指定其位置,並將文字錨定在中點。rotate轉換將標籤逆時針旋轉90度。.text(...)指定了具體的標籤內容。
然後就有了我們的圖:
視覺化資料時,D3.js很給力。這裡有一套很好的D3.js教程:http://alignedleft.com/tutorials/d3/。也推薦學習Mike Bostock的例子:https://github.com/mbostock/d3/wiki/Gallery。
邀月注:其他開源的圖表元件多的是,這裡只是原書作者的一家之言。
2.5生成直方圖
獨立安裝 Matplotlib
pip install Matplotlib
獨立安裝 Seaborn
pip install Seaborn
直方圖能幫你迅速瞭解資料的分佈。它將觀測資料分組,並以長條表示各分組中觀測資料的個數。這是個簡單而有力的工具,可檢測資料是否有問題,也可看出資料是否遵從某種已知的分佈。
本技巧將生成資料集中所有價格的直方圖。你需要用pandas和SQLAlchemy來檢索資料。Matplotlib和Seaborn處理展示層的工作。Matplotlib是用於科學資料展示的一個2D庫。Seaborn構建在Matplotlib的基礎上,併為生成統計圖表提供了一個更簡便的方法(比如直方圖等)。
我們假設資料可從mySQL資料庫取出。參考下面的程式碼將生成價格的直方圖,並儲存到PDF檔案中(data_histograms.py檔案)
1 import matplotlib.pyplot as plt 2 import pandas as pd 3 import seaborn as sns 4 import sqlalchemy as sa 5 6 # database credentials 7 usr = 'root' 8 pswd = 'downmoon' 9 dbname='test' 10 11 # create the connection to the database 12 engine = sa.create_engine( 13 'mysql+pymysql://{0}:{1}@localhost:3306/{2}?charset=utf8' \ 14 .format(usr, pswd,dbname) 15 ) 16 17 # read prices from the database 18 query = 'SELECT price FROM real_estate' 19 price = pd.read_sql_query(query, engine) 20 21 # generate the histograms 22 ax = sns.distplot( 23 price, 24 bins=10, 25 kde=True # show estimated kernel function 26 ) 27 28 # set the title for the plot 29 ax.set_title('Price histogram with estimated kernel function') 30 31 # and save to a file 32 plt.savefig('../../Data/Chapter02/Figures/price_histogram.pdf') 33 34 # finally, show the plot 35 plt.show()
原理:首先從資料庫中讀取資料。我們省略了連線資料庫的部分——參考以前的章節或者原始碼。price變數就是資料集中所有價格形成的一個列表。
用Seaborn生成直方圖很輕鬆,一行程式碼就可以搞定。.distplot(...)方法將一個數字列表(price變數)作為第一個(也是唯一必需的)引數。其餘引數都是可選項。bins引數指定了要建立多少個塊。kde引數指定是否要展示評估的核密度。
核密度評估是一個得力的非引數檢驗技巧,用來評估一個未知分佈的機率密度函式(PDF,probability density function)。核函式的積分為1(也就是說,在整個函式域上,密度函式累積起來的最大值為1),中位數為0。
.distplot(...)方法返回一個座標軸物件(參見http://matplotlib.org/api/axes_api.html)作為我們圖表的畫布。.set_title(...)方法建立了圖表的標題。
我們使用Matplotlib的.savefig(...)方法儲存圖表。唯一必需的引數是檔案儲存的路徑和名字。.savefig(...)方法足夠智慧,能從檔名的擴充套件中推斷出合適的格式。可接受的檔名擴充套件包括:原始RGBA的raw和rgba,bitmap,pdf,可縮放向量圖形的svg和svgz,封裝式Postscript的eps,jpeg或jpg,bmp.jpg,gif,pgf(LaTex的PGF程式碼),tif或tiff,以及ps(Postscript)。
最後一個方法將圖表輸出到螢幕:
2.6建立多變數的圖表
獨立安裝 Bokeh----邀月注:這是一個不小的包
pip install Bokeh
/* Installing collected packages: PyYAML, MarkupSafe, Jinja2, pillow, packaging, tornado, bokeh Successfully installed Jinja2-2.10.3 MarkupSafe-1.1.1 PyYAML-5.2 bokeh-1.4.0 packaging-19.2 pillow-6.2.1 tornado-6.0.3 FINISHED */
前一個技巧顯示,在Sacramento地區,不足兩個臥室的房屋成交量很少。在2.4節中,我們用D3.js展現價格和樓板面積之間的關係。本技巧會在這個二維圖表中加入另一個維度,臥室的數目。
Bokeh是一個結合了Seaborn和D3.js的模組:它生成很有吸引力的資料視覺化影像(就像Seaborn),也允許你透過D3.js在HTML檔案中操作圖表。用D3.js生成類似的圖表需要更多的程式碼。原始碼在data_multivariate_charts.py中:
1 # prepare the query to extract the data from the database 2 query = 'SELECT beds, sq__ft, price / 1000 AS price \ 3 FROM real_estate \ 4 WHERE sq__ft > 0 \ 5 AND beds BETWEEN 2 AND 4' 6 7 # extract the data 8 data = pd.read_sql_query(query, engine) 9 10 # attach the color based on the bed count 11 data['color'] = data['beds'].map(lambda x: colormap[x]) 12 13 # create the figure and specify label for axes 14 fig = b.figure(title='Price vs floor area and bed count') 15 fig.xaxis.axis_label = 'Price ($ \'000)' 16 fig.yaxis.axis_label = 'Feet sq' 17 18 # and plot the data 19 for i in range(2,5): 20 d = data[data.beds == i] 21 22 fig.circle(d['price'], d['sq__ft'], color=d['color'], 23 fill_alpha=.1, size=8, legend='{0} beds'.format(i)) 24 25 # specify the output HTML file 26 b.output_file( 27 '../../Data/Chapter02/Figures/price_bed_area.html', 28 title='Price vs floor area for different bed count' 29 )
原理:首先,和通常情況一樣,我們需要資料;從mySQL資料庫取出資料,這個做法你應該已經很熟悉了。然後給每一條記錄上色,這會幫助我們看出在不同臥室個數條件下價格和樓板面積的關係。colormap變數如下:
# colors for different bed count colormap = { 2: 'firebrick', 3: '#228b22', 4: 'navy' }
顏色可以指定為可讀的字串或者十六進位制值(如前面的程式碼所示)。
要將臥室個數對映到特定的顏色,我們使用了lambda。lambda是Python內建的功能,允許你用一個未命名短函式,而不是普通的函式,原地完成單個任務。它也讓程式碼更可讀。
參考下面的教程,理解為什麼lambda很有用,以及方便使用的場景:https://pythonco nquerstheuniverse.wordpress.com/2011/08/29/lambda_tutorial/。
現在我們可以建立影像。我們使用Bokeh的.figure(...)方法。你可以指定圖表的標題(正如我們所做的),以及圖形的寬和高。我們也定義座標軸的標籤,以便讀者知道他們在看什麼。
然後我們繪製資料。我們遍歷臥室可能個數的列表range(2,5)。(實際上生成的列表是[2,3,4]——參考range(...)方法的文件https://docs.python.org/3/library/stdtypes.html#typesseq-range)。對每個數字,我們提取資料的一個子集,並存在DataFrame物件d中。
對於物件d的每條記錄,我們往圖表中加一個圓圈。.circle(...)方法以x座標和y座標作為第一個和第二個引數。由於我們想看在不同臥室數目條件下,價格和麵積的關係,所以我們指定圓圈的顏色。fill_alpha引數定義了圓圈的透明度;取值範圍為[0,1],0表示完全透明,1表示完全不透明。size引數確定了圓圈有多大,legend是附加在圖表上的圖例。
Bokeh將圖表儲存為互動型HTML檔案。title引數確定了檔名。這是一份完備的HTML程式碼。其中包括了JavaScript和CSS。我們的程式碼生成下面的圖表:
圖表可以移動、縮放、調整長寬。
參考:Bokeh是非常有用的視覺化庫。要了解它的功能,可以看看這裡的示例:http://bokeh.pydata.org/en/latest/docs/gallery.html。
2.7資料取樣
有時候資料集過大,不方便建立模型。出於實用的考慮(不要讓模型的估計沒有個盡頭),最好從完整的資料集中取出一些分層樣本。
本技巧從MongoDB讀取資料,用Python取樣。
要實踐本技巧,你需要PyMongo(邀月注:可修改為mysql)、pandas和NumPy。
有兩種做法:確定一個抽樣的比例(比如說,20%),或者確定要取出的記錄條數。下面的程式碼展示瞭如何提取一定比例的資料(data_sampling.py檔案):
原理:首先確定取樣的比例,即strata_frac變數。從mySQL(邀月注:原書程式碼為MongoDB,修改為mySQL)取出資料。MongoDB返回的是一個字典。pandas的.from_dict(...)方法生成一個DataFrame物件,這樣處理起來更方便。
要獲取資料集中的一個子集,pandas的.sample(...)方法是一個很方便的途徑。不過這裡還是有一個陷阱:所有的觀測值被選出的機率相同,可能我們得到的樣本中,變數的分佈並不能代表整個資料集。
在這個簡單的例子中,為了避免前面的陷阱,我們遍歷臥室數目的取值,用.sample(...)方法從這個子集中取出一個樣本。我們可以指定frac引數,以返回資料集子集(臥室數目)的一部分。
我們還使用了DataFrame的.append(...)方法:有一個DataFrame物件(例子中的sample),將另一個DataFrame附加到這一個已有的記錄後面。ignore_index引數設為True時,會忽略附加DataFrame的索引值,並沿用原有DataFrame的索引值。
更多:有時,你會希望指定抽樣的數目,而不是佔原資料集的比例。之前說過,pandas的.sample(...)方法也能很好地處理這種場景(data_sampling_alternative.py檔案)。
1 #import pymongo 2 import pandas as pd 3 import numpy as np 4 import sqlalchemy as sa 5 6 # define a specific count of observations to get back 7 strata_cnt = 200 8 9 # name of the file to output the sample 10 w_filenameSample = \ 11 '../../Data/Chapter02/realEstate_sample2.csv' 12 13 # limiting sales transactions to those of 2, 3, and 4 bedroom 14 # properties 15 beds = [2,3,4] 16 17 # database credentials 18 usr = 'root' 19 pswd = 'downmoon' 20 dbname='test' 21 22 # create the connection to the database 23 engine = sa.create_engine( 24 'mysql+pymysql://{0}:{1}@localhost:3306/{2}?charset=utf8' \ 25 .format(usr, pswd,dbname) 26 ) 27 28 29 query = 'SELECT zip,city,price,beds,sq__ft FROM real_estate where \ 30 beds in ("2","3","4")\ 31 ' 32 sales = pd.read_sql_query(query, engine) 33 34 # calculate the expected counts 35 ttl_cnt = sales['beds'].count() 36 strata_expected_counts = sales['beds'].value_counts() / \ 37 ttl_cnt * strata_cnt 38 39 # and select the sample 40 sample = pd.DataFrame() 41 42 for bed in beds: 43 sample = sample.append( 44 sales[sales.beds == bed] \ 45 .sample(n=np.round(strata_expected_counts[bed])), 46 ignore_index=True 47 ) 48 49 # check if the counts selected match those expected 50 strata_sampled_counts = sample['beds'].value_counts() 51 print('Expected: ', strata_expected_counts) 52 print('Sampled: ', strata_sampled_counts) 53 print( 54 'Total: expected -- {0}, sampled -- {1}' \ 55 .format(strata_cnt, strata_sampled_counts.sum()) 56 ) 57 58 # output to the file 59 with open(w_filenameSample,'w') as write_csv: 60 write_csv.write(sample.to_csv(sep=',', index=False))
以上程式碼執行會報錯,解決方案如下:
/* Traceback (most recent call last): File "D:\Java2018\practicalDataAnalysis\Codes\Chapter02\data_sampling_alternative_mysql.py", line 45, in <module> .sample(n=np.round(strata_expected_counts[bed])), File "D:\tools\Python37\lib\site-packages\pandas\core\generic.py", line 4970, in sample locs = rs.choice(axis_length, size=n, replace=replace, p=weights) File "mtrand.pyx", line 847, in numpy.random.mtrand.RandomState.choice TypeError: 'numpy.float64' object cannot be interpreted as an integer .sample(n=np.round(strata_expected_counts[bed])),改為.sample(n=int(np.round(strata_expected_counts[bed]))), */
2.8將資料集拆分成訓練集、交叉驗證集和測試集
獨立安裝 Bokeh----邀月注:這是一個不小的包
pip install Bokeh
/* Installing collected packages: joblib, scikit-learn, sklearn Successfully installed joblib-0.14.1 scikit-learn-0.22 sklearn-0.0 FINISHED */
要建立一個可信的統計模型,我們需要確信它精確地抽象出了我們要處理的現象。要獲得這個保證,我們需要測試模型。要保證精確度,我們訓練和測試不能用同樣的資料集。
本技法中,你會學到如何將你的資料集快速分成兩個子集:一個用來訓練模型,另一個用來測試。
要實踐本技巧,你需要pandas、SQLAlchemy和NumPy。
我們從Mysql資料庫讀出資料,存到DataFrame裡。通常我們劃出20%~40%的資料用於測試。本例中,我們選出1/3的資料(data_split_mysql.py檔案):
1 import numpy as np 2 import pandas as pd 3 import sqlalchemy as sa 4 5 # specify what proportion of data to hold out for testing 6 test_size = 0.33 7 8 # names of the files to output the samples 9 w_filenameTrain = '../../Data/Chapter02/realEstate_train.csv' 10 w_filenameTest = '../../Data/Chapter02/realEstate_test.csv' 11 12 # database credentials 13 usr = 'root' 14 pswd = 'downmoon' 15 dbname='test' 16 17 # create the connection to the database 18 engine = sa.create_engine( 19 'mysql+mysqlconnector://{0}:{1}@localhost:3306/{2}?charset=utf8' \ 20 .format(usr, pswd,dbname) 21 ) 22 23 # read prices from the database 24 query = 'SELECT * FROM real_estate' 25 data = pd.read_sql_query(query, engine) 26 27 # create a variable to flag the training sample 28 data['train'] = np.random.rand(len(data)) < (1 - test_size) 29 30 # split the data into training and testing 31 train = data[data.train] 32 test = data[~data.train] 33 34 # output the samples to files 35 with open(w_filenameTrain,'w') as write_csv: 36 write_csv.write(train.to_csv(sep=',', index=False)) 37 38 with open(w_filenameTest,'w') as write_csv: 39 write_csv.write(test.to_csv(sep=',', index=False))
原理:
我們從指定劃分資料的比例與儲存資料的位置開始:兩個存放訓練集和測試集的檔案。
我們希望隨機選擇測試資料。這裡,我們使用NumPy的偽隨機數生成器。.rand(...)方法生成指定長度(len(data))的隨機數的列表。生成的隨機數在0和1之間。
接著我們將這些數字與要歸到訓練集的比例(1-test_size)進行比較:如果數字小於比例,我們就將記錄放在訓練集(train屬性的值為True)中;否則就放到測試集中(train屬性的值為False)。
最後兩行將資料集拆成訓練集和測試集。~是邏輯運算“否”的運算子;這樣,如果train屬性為False,那麼“否”一下就成了True。
SciKit-learn提供了另一種拆分資料集的方法。我們先將原始的資料集分成兩塊,一塊是因變數y,一塊是自變數x:
# select the independent and dependent variables x = data[['zip', 'beds', 'sq__ft']] y = data['price']
然後就可以拆了:
# and perform the split x_train, x_test, y_train, y_test = sk.train_test_split( x, y, test_size=0.33, random_state=42)
.train_test_split(...)方法幫我們將資料集拆成互補的子集:一個是訓練集,另一個是測試集。在每個種類中,我們有兩個資料集:一個包含因變數,另一個包含自變數。
Tips1、 ModuleNotFoundError: No module named 'sklearn.cross_validation' /* 在sklearn 0.18及以上的版本中,出現了sklearn.cross_validation無法匯入的情況,原因是新版本中此包被廢棄 只需將 cross_validation 改為 model_selection 即可 */ Tips2、 /* File "D:\Java2018\practicalDataAnalysis\Codes\Chapter02\data_split_alternative_mysql.py", line 40, in <module> y_train.reshape((x_train.shape[0], 1)), \ 。。。。。。。 AttributeError: 'Series' object has no attribute 'reshape' 只需 y_train.reshape((x_train.shape[0], 1)改為 y_train.values.reshape((x_train.shape[0], 1)即可 */
第2 章完。
隨書原始碼官方下載:
http://www.hzcourse.com/web/refbook/detail/7821/92