2022年9月13日

用Google Apps Script彙整試算表多張活頁的成績

同事使用Google表單設計測驗給學生進行週考,每週約有6-7科,每次考試都存至同一個Google試算表的不同活頁。

同事需要每週抓該週的數次考試成績彙整成單個試算表,做週考成績表的統整與發放。若是手工進行, 需要每頁抓取資料後排序再統整,但若遇上有一些學生缺考某些科,則需要再多花時間調整。

假設一學期週考15次,每次需要20分鐘彙整,共兩學期,這樣至少要花600分鐘進行這項工作。由於是重複的工作,應當使用程式來完成,因此我就用Google Apps script幫他寫了一隻程式。程式碼我會直接貼在此文後。

程式的邏輯是這樣:

  1. 事先在目的試算表先建立好所有學生的資料,重點是要有email欄位,因為測驗表單會紀錄學生的email,每位學生只會考一次,所以可以使用email作為index。
  2. 把目的試算表的所有email先存成array,命名為emailArray,以便後續作業。
  3. 指定來源試算表的url,讀取指定的那些活頁。讀取每個活頁,儲存活頁的名稱,歷遍每個row(每個學生的單次考試資料),取得每個row的email和分數,然後用email去找它在emailArray中的哪個位置(使用indexOf),這可以幫助定位出「這筆學生分數應該放在目的試算表的哪個row)
  4. 在目的試算表的Column部份,其欄名使用來源試算表的活頁名稱,並將該次該科學生的成績按照上一步驟得到的位址資訊填入目的試算表的該欄
  5. 當該科考試的成績填完之後,再讀取下一個指定的活頁,直到指定的活頁都讀取完。
  6. 其中會發現有些來源資料表的email未被列在目的資料表的emailArray中(使用indexOf會return -1),遇到這樣的資料則將之Logger.log印出。


使用此程式之後,每週只需要更改需要讀取的活頁index,即可快速彙整成績

程式如下。需在Google Apps Script中使用,並允許一些試算表的讀取權限。


var sourceUrl = 'https://docs.google.com/spreadsheets/d/XXXXXX/';  //成績來源試算表
var targetUrl = 'https://docs.google.com/spreadsheets/d/oooooo/';  //登錄在哪個試算表


var startSheet = 37;     //從第幾個sheet開始登錄成績
var sheetNum = 6;        //共有幾個sheet要登錄成績
var targetSheetIndex = 0 //登錄在第幾個sheet(第一個Sheet的index為0)


var emailCol = 5;        //email在第幾欄(E欄為第5欄)
var subjectColumn = 6;   //登錄從第幾欄開始(F欄為第6欄)

function getData() {
  var sourceSS = SpreadsheetApp.openByUrl(sourceUrl);
  var sourceSheets = sourceSS.getSheets();

  var targetSS = SpreadsheetApp.openByUrl(targetUrl);
  var targetSheets = targetSS.getSheets();  
  var targetRowNum = targetSheets[targetSheetIndex].getLastRow();

  // 從targetSheet獲取email array。從2d array變成 1d array
  var emailArray = targetSheets[targetSheetIndex].getRange(2,emailCol,targetRowNum-1,1).getValues().flat();
  
  //歷遍每個週考的sheet
  for(var i = startSheeti < startSheet + sheetNum ; i++) {
    var subject = sourceSheets[i].getName();

    // 在targetSheet寫入科目欄名
    targetSheets[targetSheetIndex].getRange(1,subjectColumn ,1 ,1).setValue(subject);

    //尋找該科目在array的哪個index
    var rowNum = sourceSheets[i].getLastRow();
    
    // 到每頁成績表的每列找資料,紀錄email欄位和科目欄位
    for (var j = 2j <= rowNumj++) {
      var email = sourceSheets[i].getRange(j2).getValue();
      var score = sourceSheets[i].getRange(j3).getValue();

      // 到彙整試算表找email相等的欄位,填入對應的科目分數
      var emailPos = emailArray.indexOf(email);
      if (emailPos > -1) {
        targetSheets[targetSheetIndex].getRange(emailPos + 2subjectColumn).setValue(score);
      }
      else{
        Logger.log(email + " " + subject + " "score);
      }
    }
    subjectColumn += 1;
  }
}

function main(){
  getData();
}

2022年9月5日

會考分析的程式實作方法

上週把心測中心的資料用程式做出了一個分析檔案,詳見此篇

以下就紀錄一些整個文件的製作想法。

分析後的結果內容是一個PDF,按照答題率高低分頁,每頁一題共三張圖,圖一是把各題的選項答題率畫成折線圖,圖二是把通過率和鑑別度畫成散佈圖再把該題的位置標上去,圖三再加上題目截圖,

其實完全可以用手工製作,但是做完應該會往生。我做了四個年度的考題分析,以111年度的考題共有214題,而108~109的題目數量則較多一些。每題要三張圖。所以共計大約要900張圖。如果做錯一個部份的話,就要再從來一次。所以盡可能都要用程式去完成,才能省工。

首先是試題分析word檔,因為是直接透過雲端硬碟開啟的,所以我是從google文件上複製下來,再貼到gnumeric上存成ods檔,然後再用python讀取每一題的每一選項的答題率和鑑別度、通過率這些。由於我工作電腦是linux,所以使用gnumeric和ods檔對我最方便,若是在windos電腦上,使用xlsx亦可。

讀取後存成dataframe,再去讀取每一題的標準答案,將每一題的標準答案畫在圖表的第一個位置,再將剩下三個選項按照全體答題率的順序畫出。其他群題如基礎或待加強的學生的選項順序也是依照全體的資料來排。這可以讓使用者直接看到各個不同群體和全體的差異是什麼?若是以最簡單的題目來看,則這些群體的答題曲線都是類似的。而通過率越低的題目,它的答題曲線就會越特別。

接下來的第二張圖的繪製,以前就寫了程式,所以直接複製過來就行。讀取該科該年段所有題目的通過率和鑑別度畫成散布圖,再以每一題的資料各自畫出一張圖。好處是你可以看到整體題目的分佈,是不是有離群值?大致分佈的通過率範圍在哪裡?都可以一目了然。

第三張圖不過就是題目截圖,花了一些時間研究如何達成。先用python解析pdf文檔,解析完會得到許多文字框的資訊,包含文字框的座標和內容。但PDF文字框的規則不知道是怎麼做的?有些是一整個題目就是一個文字框,但有些又不是。而我需要的是找出每一題的座標在哪裡,作法是在歷遍各個文字框的內容找出「題號」和「D選項」分別在哪些文字框中,紀錄該文字框所在的頁面、Y座標。然後再根據這些Y座標找出每一題的起訖座標,再根據這些座標截圖下來。這邊要提醒的是,座標原點在左下角,所以Y座標是向上為正。

本來這樣的邏輯應該可以順利解出所有的題目圖檔,但是偏偏各科的排版方式都不太一樣,所以我會再人工檢查每一題的圖檔是否正確,若是有錯則是人工再截圖過。

做完以上三張圖之後,其實工作就完成了八九成了,剩下就是把三張圖合成一張圖,再根據通過率的高低,排出一個PDF檔。

2022年9月3日

各科會考全分析

上週從簡欲尋老師的臉書,看到了她分享的過去幾年由心測中心提供的會考各科各題答題率的資料。

心測中心公佈的都是一群數字的「資料」,真要從裡頭看出端倪,還需要透過整理與視覺化才能成為有意義的「資訊」,再經過學科專家的判斷解讀成為「知識」,然後才能運用知識解決實際問題,形成「智慧」。

資料長什麼樣呢?一群數字,實在看不出什麼東西對吧?


為了把資料轉成資訊,我寫了幾支程式,為各科做出了像這樣的PDF檔,每頁的順序是通過率由低到高。每頁包含了三張圖,分別是各選項答題率、該題在整體的通過率和鑑別度的位置,另外就是題目圖檔。


在各選項答題率的圖中,X軸的排序規則是:第一個是標準答案,以這題來說,就是D選項。接下來的排序為ACB,代表扣掉標準答案後,全體回答率的順序為ACB。
以下圖的解讀就是「此題標準答案為D,而『基礎』的學生群體,大多回答A選項,『待加強』的學生群體則是大多回答C選項」,而在通過率和鑑別度的圖中,紅點為此題的分佈位置,代表「本次會考自然科中,此題是通過率最低,鑑別度也最低的題目。」


再來看一題,是111年會考自然科的第一題。從左下圖可以看出,無論基礎或待加強的學生,對此題的作答反應都相似,而這樣的答題曲線也反映在右下圖,此題的鑑別度不高(因為高分低分都可以答對)



我製作了兩種版本的PDF檔案,分別是以題號排序和以通過率排序,你可以從這裡看到這些檔案。

https://drive.google.com/drive/folders/1lGwWoDVc5P8RNIn5y16-U33w38b6cIct?usp=sharing


你可以怎麼用?

  1. 開啟通過率排序的檔案,檢視前10頁,那代表前十大難題。使用全螢幕檢視或是簡報模式觀察這個PDF,看看這些題目的答題曲線有什麼特徵?看看待加強的學生最常答哪些選項?分析看看他們為什麼這麼作。
  2. 看看最簡單的題目答題曲線(最後一頁),如果把它當作基本的圖形,它像是個反J型。去找找看哪些題目的圖形不是反J型,例如下圖。通常代表某些選項有強的誘答力,思考看看為什麼?想想學生是怎麼想的?

除了這些觀察點以外,你還可以看什麼呢?