FITSファイルの重複行を調査する方法とエンディアンに関する注意点

# **はじめに**

天文学の分野では、観測データをFITS（Flexible Image Transport System）形式で保存するのが一般的です。しかし、時にはデータ内に重複する行が含まれることがあります。本記事では、**擬似的なFITSファイルを生成し、pandasを使って重複行を検出する方法**を紹介します。

また、**numpy**と**pandas**のエンディアンの取り扱いに関する違いにも注意を向け、エンディアンの変換が必要な場合の対処法を解説します。

---

## **目次**
1. FITSファイルとエンディアンの取り扱いの違い
2. 擬似FITSファイルの生成
3. pandasを使った重複行の検出と保存
4. エンディアンの確認と変換の方法
5. サンプルコードと実行例

---

## **1. FITSファイルとエンディアンの取り扱いの違い**

FITSファイルは、古いハードウェアや異なるプラットフォーム間の互換性を確保するため、**ビッグエンディアン（Big-endian）形式** でデータを保存することが一般的です。
一方、`numpy`と`pandas`はデフォルトで **リトルエンディアン（Little-endian）** を使用します。

このエンディアンの不一致が原因で、`pandas`でデータを処理する際にエラーが発生することがあります。

---

## **2. 擬似FITSファイルの生成**

以下のコードは、イベントデータを持つ擬似的なFITSファイルを生成します。

### **FITSファイル生成コード**

``` python:create_mock_fits.py
from astropy.io import fits
import numpy as np

def create_mock_fits(filename):
    """Generate a mock FITS file with event data, including duplicates."""
    n_rows = 20
    time = np.concatenate([np.linspace(0, 19, 20), [10]])  # Duplicate TIME at 10
    itype = np.random.randint(0, 8, n_rows + 1)
    pixel = np.random.randint(0, 36, n_rows + 1)
    trig_lp = np.random.randint(0, 1000, n_rows + 1)
    deriv_max = np.random.randint(-32768, 32767, n_rows + 1)

    cols = [
        fits.Column(name='TIME', format='D', array=time),
        fits.Column(name='ITYPE', format='B', array=itype),
        fits.Column(name='PIXEL', format='B', array=pixel),
        fits.Column(name='TRIG_LP', format='J', array=trig_lp),
        fits.Column(name='DERIV_MAX', format='I', array=deriv_max)
    ]

    hdu = fits.BinTableHDU.from_columns(cols)
    hdu.writeto(filename, overwrite=True)
    print(f"Mock FITS file created: {filename}")

create_mock_fits('mock_event_data.fits')
```

---

## **3. pandasを使った重複行の検出と保存**

以下のコードは、FITSファイルからデータを読み込み、`TIME`に基づいて重複行を検出し、新しいFITSファイルに保存します。

### **重複行の検出コード**

```python:find_duplicates.py
from astropy.io import fits
import pandas as pd

def load_fits_to_dataframe(fits_file):
    """Load FITS data into a pandas DataFrame with endian conversion."""
    with fits.open(fits_file) as hdul:
        data = hdul[1].data
        # Ensure all data is converted to Little-endian for pandas compatibility
        df = pd.DataFrame({
            'TIME': data['TIME'].byteswap().newbyteorder(),
            'ITYPE': data['ITYPE'].byteswap().newbyteorder(),
            'PIXEL': data['PIXEL'].byteswap().newbyteorder(),
            'TRIG_LP': data['TRIG_LP'].byteswap().newbyteorder(),
            'DERIV_MAX': data['DERIV_MAX'].byteswap().newbyteorder()
        })
    return df

def find_and_save_duplicates(df, output_file):
    """Find duplicate rows based on 'TIME' and save them to a new FITS file."""
    duplicates = df[df.duplicated('TIME', keep=False)]
    print(f"Found {len(duplicates)} duplicate rows.")

    if not duplicates.empty:
        hdu = fits.BinTableHDU.from_columns([
            fits.Column(name='TIME', format='D', array=duplicates['TIME']),
            fits.Column(name='ITYPE', format='B', array=duplicates['ITYPE']),
            fits.Column(name='PIXEL', format='B', array=duplicates['PIXEL']),
            fits.Column(name='TRIG_LP', format='J', array=duplicates['TRIG_LP']),
            fits.Column(name='DERIV_MAX', format='I', array=duplicates['DERIV_MAX'])
        ])
        hdu.writeto(output_file, overwrite=True)
        print(f"Duplicate rows saved to {output_file}")

df = load_fits_to_dataframe('mock_event_data.fits')
find_and_save_duplicates(df, 'duplicates.fits')
```

---

## **4. エンディアンの確認と変換の方法**

### **エンディアンの確認**

以下のコードで、`numpy`配列のエンディアンを確認できます。

```python
print(df['TIME'].dtype)  # >f8: Big-endian, <f8: Little-endian
```

- `>f8`：Big-endian  
- `<f8`：Little-endian

### **エンディアンの変換**

以下のように、`byteswap()`と`newbyteorder()`を使ってエンディアンを変換します。

```python
df['TIME'] = df['TIME'].byteswap().newbyteorder()
```

---

## **5. サンプルコードと実行例**

### **実行手順**

1. **擬似FITSファイルを生成**:
   ```bash:
   python create_mock_fits.py
   ```

   FITSファイルの中身を`ftlist`を用いて確認します。
   
```bash:
> ftlist mock_event_data.fits
Print options: H C K I T [C] T

                       TIME ITYP PIXE     TRIG_LP DERIV_
                                                        
  1        0.00000000000000    4   28         503 -14406
  2        1.00000000000000    1   26         368   5503
  3        2.00000000000000    6   19         350  -3004
  4        3.00000000000000    4   31         948  32573
  5        4.00000000000000    7    0         494 -14526
  6        5.00000000000000    5   32          51 -29495
  7        6.00000000000000    7    9         496 -25354
  8        7.00000000000000    5    9         572  32754
  9        8.00000000000000    3   24          51  14589
 10        9.00000000000000    2   32         658 -13708
 11        10.0000000000000    7    2         294  32600
 12        11.0000000000000    7   10         281 -27159
 13        12.0000000000000    2   11         703  27509
 14        13.0000000000000    2   14         283   7499
 15        14.0000000000000    7   24         802  30085
 16        15.0000000000000    1    4         897  22922
 17        16.0000000000000    5   33         939 -30371
 18        17.0000000000000    3   32         677   7238
 19        18.0000000000000    2   28         960   6749
 20        19.0000000000000    2   34         181  24114
 21        10.0000000000000    6   25         644  -8361
 ```

2. **重複行を検出して保存**:
   ```bash
   python find_duplicates.py
   ```

### **出力例**

```
Mock FITS file created: mock_event_data.fits
Found 2 duplicate rows.
Duplicate rows saved to duplicates.fits
```

ファイルの中身も確認しましょう。

``` bash:
>ftlist duplicates.fits     
                       TIME ITYP PIXE     TRIG_LP DERIV_
                                                        
  1        10.0000000000000    7    2         294  32600
  2        10.0000000000000    6   25         644  -8361
```

TIMEが 10.0 の部分だけが検出できていますね。

---

## **まとめ**

本記事では、擬似FITSファイルの生成から重複行の検出、エンディアンの変換方法について説明しました。FITSファイルの解析では、`numpy`と`pandas`のエンディアンの違いに注意が必要です。必要に応じてエンディアンをリトルエンディアンに変換し、データの互換性を確保しましょう。

---

## **参考文献**

- [Astropy Documentation](https://www.astropy.org/)
- [pandas Documentation](https://pandas.pydata.org/)