匿名希望のおでんFortranツヴァイさん太郎

生き物、Fortran、川について書く

pack関数の使い方

Fortranの配列用組み込み関数の中にpack関数というものがあります.直観的に配列を扱うことができ,データをまとめたりする際に大変便利な関数です

pack関数は配列xの要素の内,条件を満たす要素(正確にはmaskで指定した論理型配列の成分が真である部分の要素*1)のみを取り出して新しい配列を作る関数です.このように書いてもわかりにくいので,具体的に使い方を示します.

以下のような魚の調査をしたとしましょう.

  • Station 1からStation 10において,ある魚種の体長を調べる
  • 各Stationの底質が砂(Sand)か砂利(Gravel)かを記録する

この調査から以下ようなデータ(data.csv)が得られます.なお,全て架空データです.
このデータから,最小体長,最大体長,平均体長,標準偏差を,調査個体全体,砂場の個体のみ,砂利場の個体のみの三つの区分で算出します.

入力データ

Station, River_bed, TL(cm)
      1,     Sand ,   10.0 
      1,     Sand ,   17.2 
      1,     Sand ,   19.3 
      1,     Sand ,   17.4 
      1,     Sand ,   18.5 
      2,   Gravel ,   13.1 
      2,   Gravel ,   18.8 
      2,   Gravel ,   18.9 
      3,   Gravel ,   11.8 
      3,   Gravel ,   19.6 
      4,     Sand ,   11.7 
      4,     Sand ,   17.3 
      4,     Sand ,   14.0 
      4,     Sand ,   18.0 
      5,     Sand ,   20.1 
      5,     Sand ,   22.0 
      6,     Sand ,   16.0 
      7,   Gravel ,   17.3 
      7,   Gravel ,   16.5 
      7,   Gravel ,   12.0 
      8,     Sand ,   19.8 
      8,     Sand ,   16.7 
      8,     Sand ,   14.9 
      9,   Gravel ,   18.4 
      9,   Gravel ,   13.1 
     10,     Sand ,   13.6 
     10,     Sand ,   11.5 
     10,     Sand ,   15.9 


プログラム

module stat_calc
implicit none

contains

subroutine input(unit_num, file_name, st, bed, tl)
	integer, intent(in)::unit_num
	character(*), intent(in)::file_name
	integer, allocatable, intent(out)::st(:)
	character(*), allocatable, intent(out)::bed(:)
	double precision, allocatable, intent(out)::tl(:)
	!    Loop
	integer line_counter
	integer i

	open(unit_num, file=file_name, status="old", action="read")
	
	line_counter = 0
	read(unit_num, '()') ! ヘッダー行読み飛ばし
	do
		read(unit_num, *, end=99) 
		line_counter = line_counter + 1
	end do
	99 continue
	
	rewind(unit_num) ! ファイルを開きなおして,一行目から読み直し

	allocate(st(line_counter))
	allocate(bed(line_counter))
	allocate(tl(line_counter))

	read(unit_num, '()') ! ヘッダー行読み飛ばし
	do i = 1, line_counter
		read(unit_num, *) st(i), bed(i), tl(i)
	end do

	close(unit_num)
end subroutine


!    平均を求める
function dmean(x) result(rslt)
	double precision, intent(in)::x(:)
	!    Temporary
	double precision rslt

	rslt = dble(sum(x)) / dble(size(x))
end function


!    標準偏差を求める
function dsd(x) result(rslt)
	double precision, intent(in)::x(:)
	!    Temporary
	double precision var
	double precision rslt

	var = dot_product(x - dmean(x), x - dmean(x)) / dble(size(x))
	rslt = sqrt(var)
end function

end module


program main
use stat_calc
implicit none
!    Input
integer, allocatable::st(:)           ! 調査地点番号
character(len=6), allocatable::bed(:) ! 底質
double precision, allocatable::tl(:)  ! 全長
!    Temporary
double precision, allocatable::sand_tl(:)   !    Sandの場のtlだけを集めたもの
double precision, allocatable::gravel_tl(:) !    Gravelの場のtlだけを集めたもの
integer n_sand   ! 砂場の総データ数
integer n_gravel ! 砂利場の総データ数

call input(17, "data.csv", st, bed, tl)

!    packした時の大きさを取得
!    pack関数の書き方は,pack(packされるもの,maskの条件式)
!    bed=="Sand"により,bedと同じ大きさで,"Sand"の部分に相当する要素が.true.,
!    "Gravel"に相当する要素が.false.である論理型配列が指定される
!    bed=="Gravel"の場合も同様に論理型配列を指定している
n_sand = size(pack(tl, bed=="Sand"))     
n_gravel = size(pack(tl, bed=="Gravel")) 

allocate(sand_tl(n_sand))
allocate(gravel_tl(n_gravel))

sand_tl(:) = pack(tl, bed=="Sand")     ! Sandの部分のtlを抽出
gravel_tl(:) = pack(tl, bed=="Gravel") ! Gravelの部分のtlを抽出

print*, "砂場のデータ数  ", n_sand
print*, "砂利場のデータ数", n_gravel

open(17, file="summary.csv", status="replace")
write(17, '(3(A, ","), A)') "", "Total", "Sand", "Gravel"
write(17, '(A, ",", 2(i3, ","), i3)')     "No_individual",  size(tl), n_sand, n_gravel
write(17, '(A, ",", 2(f5.1, ","), f5.1)') "Min_TL"     , minval(tl) , minval(sand_tl), minval(gravel_tl)
write(17, '(A, ",", 2(f5.1, ","), f5.1)') "Max_TL"     , maxval(tl) , maxval(sand_tl), maxval(gravel_tl)
write(17, '(A, ",", 2(f5.2, ","), f5.2)') "Ave_TL"     ,  dmean(tl) ,  dmean(sand_tl),  dmean(gravel_tl)
write(17, '(A, ",", 2(f5.2, ","), f5.2)') "SD_TL"      ,    dsd(tl) ,    dsd(sand_tl),    dsd(gravel_tl)
close(17)

end program

画面出力

 砂場のデータ数            18
 砂利場のデータ数          10

出力ファイル

              , Total,  Sand, Gravel 
 No_individual,    28,    18,      10
        Min_TL,  10.0,  10.0,    11.8
        Max_TL,  22.0,  22.0,    19.6
        Ave_TL, 16.19, 16.33,   15.95
         SD_TL,  3.08,  3.14,    2.96


今回のデータでは区分は底質だけですが,実際の調査ではもっと多くの類型にわけられます.例えば,植物の有無や他魚種の有無でデータを類型化し,類型ごとに今回のような基本統計量やヒストグラムの作成をすることがしばしばあります.

このような時にpack関数によっていじりたいデータだけを抽出すると,プログラムが読みやすく,しかも必要に応じて変更しやすいものになります.

*1:maskや論理型についてはこちら y-tis.hatenablog.com