[official skill] XLSX μμ±
μ΄ μ€ν¬μ openpyxlκ³Ό pandasλ₯Ό μ¬μ©νμ¬ Excel μ€νλ λμνΈλ₯Ό μμ±, νΈμ§, λΆμνλ©° μ¬λ¬΄ λͺ¨λΈλ§ νμ€μ μ€μνλ λꡬμ λλ€.
π μ°Έκ³ : μ΄ λ¬Έμλ 곡μ Claude Code λ¬Έμμ μλ¬Έ + λ νΌλ°μ€μ λλ€.
- μλ¬Έ λ§ν¬λ₯Ό μ§μ μ¬μ©νμ λ λ©λλ€
- μ΄ νμΌμ
.claude/skills/νμμ 볡μ¬νμ¬ μ€ν¬λ‘ λ±λ‘ν μλ μμ΅λλ€(2026.03.15 κΈ°μ€)
μΆλ ₯λ¬Ό μꡬμ¬ν
λͺ¨λ Excel νμΌ
μ λ¬Έμ μΈ ν°νΈ
- μ¬μ©μκ° λ³λλ‘ μ§μνμ§ μλ ν λͺ¨λ κ²°κ³Όλ¬Όμ μΌκ΄λκ³ μ λ¬Έμ μΈ ν°νΈ(μ: Arial, Times New Roman)λ₯Ό μ¬μ©νμΈμ
μμ μ€λ₯ μ λ‘
- λͺ¨λ Excel λͺ¨λΈμ μμ μ€λ₯(#REF!, #DIV/0!, #VALUE!, #N/A, #NAME?) μμ΄ μ 곡λμ΄μΌ ν©λλ€
κΈ°μ‘΄ ν νλ¦Ώ μ μ§ (ν νλ¦Ώ μ λ°μ΄νΈ μ)
- νμΌ μμ μ κΈ°μ‘΄ νμ, μ€νμΌ, κ·μΉμ μ νν νμ νκ³ μΌμΉμν€μΈμ
- μ΄λ―Έ ν립λ ν¨ν΄μ΄ μλ νμΌμ νμ€νλ μμμ κ°μνμ§ λ§μΈμ
- κΈ°μ‘΄ ν νλ¦Ώ κ·μΉμ νμ μ΄ κ°μ΄λλΌμΈλ³΄λ€ μ°μ ν©λλ€
μ¬λ¬΄ λͺ¨λΈ
μμ μ½λ© νμ€
μ¬μ©μλ κΈ°μ‘΄ ν νλ¦Ώμμ λ³λλ‘ λͺ μνμ§ μλ ν
μ κ³ νμ€ μμ κ·μΉ
- νλμ ν μ€νΈ (RGB: 0,0,255): νλμ½λ©λ μ λ ₯κ°, μλ리μ€μ©μΌλ‘ μ¬μ©μκ° λ³κ²½ν μ«μ
- κ²μμ ν μ€νΈ (RGB: 0,0,0): λͺ¨λ μμκ³Ό κ³μ°
- λ Ήμ ν μ€νΈ (RGB: 0,128,0): λμΌ μν¬λΆ λ΄ λ€λ₯Έ μν¬μνΈμμ κ°μ Έμ€λ λ§ν¬
- λΉ¨κ°μ ν μ€νΈ (RGB: 255,0,0): λ€λ₯Έ νμΌλ‘μ μΈλΆ λ§ν¬
- λ Έλμ λ°°κ²½ (RGB: 255,255,0): μ£Όμκ° νμν ν΅μ¬ κ°μ λλ μ λ°μ΄νΈκ° νμν μ
μ«μ μμ νμ€
νμ μμ κ·μΉ
- μ°λ: ν μ€νΈ λ¬Έμμ΄λ‘ μμ (μ: "2024"μ΄μ§ "2,024"κ° μλ)
- ν΅ν: $#,##0 μμ μ¬μ©; νμ ν€λμ λ¨μλ₯Ό λͺ μ ("Revenue ($mm)")
- 0κ°: μ«μ μμμ μ¬μ©νμ¬ λͺ¨λ 0μ "-"λ‘ νμ, λ°±λΆμ¨ ν¬ν¨ (μ: "$#,##0;($#,##0);-")
- λ°±λΆμ¨: κΈ°λ³Έμ μΌλ‘ 0.0% μμ (μμμ ν μ리)
- λ°°μ: λ°Έλ₯μμ΄μ λ°°μμ 0.0x μμ (EV/EBITDA, P/E)
- μμ: λ§μ΄λμ€(-123)κ° μλ κ΄νΈ(123) μ¬μ©
μμ μμ± κ·μΉ
κ°μ λ°°μΉ
- λͺ¨λ κ°μ (μ±μ₯λ₯ , λ§μ§, λ°°μ λ±)μ λ³λμ κ°μ μ μ λ°°μΉ
- μμμμ νλμ½λ©λ κ° λμ μ μ°Έμ‘° μ¬μ©
- μ: =B51.05 λμ =B5(1+$B$6) μ¬μ©
μμ μ€λ₯ λ°©μ§
- λͺ¨λ μ μ°Έμ‘°κ° μ¬λ°λ₯Έμ§ νμΈ
- λ²μμ off-by-one μ€λ₯ νμΈ
- λͺ¨λ μΆμ κΈ°κ°μ κ±Έμ³ μΌκ΄λ μμ 보μ₯
- μμ§ μΌμ΄μ€λ‘ ν μ€νΈ (0κ°, μμ)
- μλνμ§ μμ μν μ°Έμ‘°κ° μλμ§ νμΈ
νλμ½λ λ¬Έμν μꡬμ¬ν
- μ½λ©νΈ λλ μμ μ μ κΈ°λ‘ (ν λμΈ κ²½μ°). νμ: "Source: [μμ€ν /λ¬Έμ], [λ μ§], [ꡬ체μ μ°Έμ‘°], [ν΄λΉνλ κ²½μ° URL]"
- μμ:
- "Source: Company 10-K, FY2024, Page 45, Revenue Note, [SEC EDGAR URL]"
- "Source: Company 10-Q, Q2 2025, Exhibit 99.1, [SEC EDGAR URL]"
- "Source: Bloomberg Terminal, 8/15/2025, AAPL US Equity"
- "Source: FactSet, 8/20/2025, Consensus Estimates Screen"
XLSX μμ±, νΈμ§, λΆμ
κ°μ
μ¬μ©μκ° .xlsx νμΌμ μμ±, νΈμ§, λλ λ΄μ© λΆμμ μμ²ν μ μμ΅λλ€. μμ μ λ°λΌ λ€μν λꡬμ μν¬νλ‘μ°λ₯Ό μ¬μ©ν μ μμ΅λλ€.
μ€μ μꡬμ¬ν
μμ μ¬κ³μ°μ LibreOffice νμ: scripts/recalc.py μ€ν¬λ¦½νΈλ₯Ό μ¬μ©ν μμ μ¬κ³μ°μ μν΄ LibreOfficeκ° μ€μΉλμ΄ μλ€κ³ κ°μ ν μ μμ΅λλ€. μ΄ μ€ν¬λ¦½νΈλ Unix μμΌμ΄ μ νλ μλλ°μ€ νκ²½μ ν¬ν¨νμ¬ μ²« μ€ν μ LibreOfficeλ₯Ό μλμΌλ‘ μ€μ ν©λλ€ (scripts/office/soffice.pyκ° μ²λ¦¬).
λ°μ΄ν° μ½κΈ° λ° λΆμ
pandasλ₯Ό μ¬μ©ν λ°μ΄ν° λΆμ
λ°μ΄ν° λΆμ, μκ°ν, κΈ°λ³Έ μμ μλ κ°λ ₯ν λ°μ΄ν° μ‘°μ κΈ°λ₯μ μ 곡νλ pandasλ₯Ό μ¬μ©νμΈμ:
import pandas as pd
# Excel μ½κΈ°
df = pd.read_excel('file.xlsx') # κΈ°λ³Έκ°: 첫 λ²μ§Έ μνΈ
all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # λͺ¨λ μνΈλ₯Ό dictλ‘
# λΆμ
df.head() # λ°μ΄ν° 미리보기
df.info() # μ΄ μ 보
df.describe() # ν΅κ³
# Excel μ°κΈ°
df.to_excel('output.xlsx', index=False)
Excel νμΌ μν¬νλ‘μ°
μ€μ: νλμ½λ©λ κ°μ΄ μλ μμμ μ¬μ©νμΈμ
νμ Pythonμμ κ°μ κ³μ°νκ³ νλμ½λ©νλ λμ Excel μμμ μ¬μ©νμΈμ. μ΄λ κ² νλ©΄ μ€νλ λμνΈκ° λμ μ΄κ³ μ λ°μ΄νΈ κ°λ₯ν μνλ₯Ό μ μ§ν©λλ€.
WRONG - κ³μ°λ κ° νλμ½λ©
# λμ¨: Pythonμμ κ³μ°νκ³ κ²°κ³Όλ₯Ό νλμ½λ©
total = df['Sales'].sum()
sheet['B10'] = total # 5000μ νλμ½λ©
# λμ¨: Pythonμμ μ±μ₯λ₯ κ³μ°
growth = (df.iloc[-1]['Revenue'] - df.iloc[0]['Revenue']) / df.iloc[0]['Revenue']
sheet['C5'] = growth # 0.15λ₯Ό νλμ½λ©
# λμ¨: PythonμΌλ‘ νκ· κ³μ°
avg = sum(values) / len(values)
sheet['D20'] = avg # 42.5λ₯Ό νλμ½λ©
CORRECT - Excel μμ μ¬μ©
# μ’μ: Excelμμ ν©κ³λ₯Ό κ³μ°νκ² νκΈ°
sheet['B10'] = '=SUM(B2:B9)'
# μ’μ: μ±μ₯λ₯ μ Excel μμμΌλ‘
sheet['C5'] = '=(C4-C2)/C2'
# μ’μ: Excel ν¨μλ‘ νκ·
sheet['D20'] = '=AVERAGE(D2:D19)'
μ΄λ λͺ¨λ κ³μ°μ μ μ©λ©λλ€ - ν©κ³, λ°±λΆμ¨, λΉμ¨, μ°¨μ΄ λ±. μ€νλ λμνΈλ μλ³Έ λ°μ΄ν°κ° λ³κ²½λ λ μ¬κ³μ°ν μ μμ΄μΌ ν©λλ€.
μΌλ° μν¬νλ‘μ°
- λꡬ μ ν: λ°μ΄ν°μ© pandas, μμ/μμμ© openpyxl
- μμ±/λ‘λ: μ μν¬λΆ μμ± λλ κΈ°μ‘΄ νμΌ λ‘λ
- μμ : λ°μ΄ν°, μμ, μμ μΆκ°/νΈμ§
- μ μ₯: νμΌλ‘ μ°κΈ°
- μμ μ¬κ³μ° (μμ μ¬μ© μ νμ): scripts/recalc.py μ€ν¬λ¦½νΈ μ¬μ©
python scripts/recalc.py output.xlsx - μ€λ₯ νμΈ λ° μμ :
- μ€ν¬λ¦½νΈλ μ€λ₯ μΈλΆμ¬νμ΄ ν¬ν¨λ JSONμ λ°νν©λλ€
statusκ°errors_foundμ΄λ©΄error_summaryμμ νΉμ μ€λ₯ μ νκ³Ό μμΉλ₯Ό νμΈνμΈμ- νμΈλ μ€λ₯λ₯Ό μμ νκ³ λ€μ μ¬κ³μ°νμΈμ
- μμ ν΄μΌ ν μΌλ°μ μΈ μ€λ₯:
#REF!: μ ν¨νμ§ μμ μ μ°Έμ‘°#DIV/0!: 0μΌλ‘ λλκΈ°#VALUE!: μμμμ μλͺ»λ λ°μ΄ν° νμ#NAME?: μΈμλμ§ μλ μμ μ΄λ¦
μ Excel νμΌ μμ±
# μμκ³Ό μμμ μν΄ openpyxl μ¬μ©
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
wb = Workbook()
sheet = wb.active
# λ°μ΄ν° μΆκ°
sheet['A1'] = 'Hello'
sheet['B1'] = 'World'
sheet.append(['Row', 'of', 'data'])
# μμ μΆκ°
sheet['B2'] = '=SUM(A1:A10)'
# μμ
sheet['A1'].font = Font(bold=True, color='FF0000')
sheet['A1'].fill = PatternFill('solid', start_color='FFFF00')
sheet['A1'].alignment = Alignment(horizontal='center')
# μ΄ λλΉ
sheet.column_dimensions['A'].width = 20
wb.save('output.xlsx')
κΈ°μ‘΄ Excel νμΌ νΈμ§
# μμκ³Ό μμμ μ μ§νκΈ° μν΄ openpyxl μ¬μ©
from openpyxl import load_workbook
# κΈ°μ‘΄ νμΌ λ‘λ
wb = load_workbook('existing.xlsx')
sheet = wb.active # λλ νΉμ μνΈλ₯Ό μν΄ wb['SheetName']
# μ¬λ¬ μνΈ μμ
for sheet_name in wb.sheetnames:
sheet = wb[sheet_name]
print(f"Sheet: {sheet_name}")
# μ
μμ
sheet['A1'] = 'New Value'
sheet.insert_rows(2) # 2λ² μμΉμ ν μ½μ
sheet.delete_cols(3) # 3λ² μ΄ μμ
# μ μνΈ μΆκ°
new_sheet = wb.create_sheet('NewSheet')
new_sheet['A1'] = 'Data'
wb.save('modified.xlsx')
μμ μ¬κ³μ°
openpyxlλ‘ μμ±νκ±°λ μμ ν Excel νμΌμλ μμμ΄ λ¬Έμμ΄λ‘ ν¬ν¨λμ΄ μμ§λ§ κ³μ°λ κ°μ μμ΅λλ€. μ 곡λ scripts/recalc.py μ€ν¬λ¦½νΈλ₯Ό μ¬μ©νμ¬ μμμ μ¬κ³μ°νμΈμ:
python scripts/recalc.py <excel_file> [timeout_seconds]
μμ:
python scripts/recalc.py output.xlsx 30
μ΄ μ€ν¬λ¦½νΈλ:
- 첫 μ€ν μ LibreOffice λ§€ν¬λ‘λ₯Ό μλμΌλ‘ μ€μ ν©λλ€
- λͺ¨λ μνΈμ λͺ¨λ μμμ μ¬κ³μ°ν©λλ€
- λͺ¨λ μ μμ Excel μ€λ₯(#REF!, #DIV/0! λ±)λ₯Ό μ€μΊν©λλ€
- μμΈν μ€λ₯ μμΉμ κ°μκ° ν¬ν¨λ JSONμ λ°νν©λλ€
- Linuxμ macOS λͺ¨λμμ λμν©λλ€
μμ κ²μ¦ 체ν¬λ¦¬μ€νΈ
μμμ΄ μ¬λ°λ₯΄κ² λμνλμ§ νμΈνκΈ° μν λΉ λ₯Έ μ κ²:
νμ κ²μ¦
- μν μ°Έμ‘° 2-3κ° ν μ€νΈ: μ 체 λͺ¨λΈμ ꡬμΆνκΈ° μ μ μ¬λ°λ₯Έ κ°μ κ°μ Έμ€λμ§ νμΈ
- μ΄ λ§€ν: Excel μ΄μ΄ μΌμΉνλμ§ νμΈ (μ: 64λ²μ§Έ μ΄ = BL, BKκ° μλ)
- ν μ€νμ : Excel νμ 1λΆν° μμνλ€λ μ κΈ°μ΅ (DataFrame ν 5 = Excel ν 6)
μΌλ°μ μΈ ν¨μ
- NaN μ²λ¦¬:
pd.notna()λ‘ null κ° νμΈ - 맨 μ€λ₯Έμͺ½ μ΄: FY λ°μ΄ν°λ μ’ μ’ 50λ²μ§Έ μ΄μμ μ΄μ μμΉ
- λ€μ€ λ§€μΉ: 첫 λ²μ§ΈλΏλ§ μλλΌ λͺ¨λ λ°μμ κ²μ
- 0μΌλ‘ λλκΈ°: μμμμ
/λ₯Ό μ¬μ©νκΈ° μ μ λΆλͺ¨ νμΈ (#DIV/0!) - μλͺ»λ μ°Έμ‘°: λͺ¨λ μ μ°Έμ‘°κ° μλν μ μ κ°λ¦¬ν€λμ§ νμΈ (#REF!)
- μνΈ κ° μ°Έμ‘°: μνΈ μ°κ²°μ μ¬λ°λ₯Έ νμ(Sheet1!A1) μ¬μ©
μμ ν μ€νΈ μ λ΅
- μκ² μμ: κ΄λ²μνκ² μ μ©νκΈ° μ μ 2-3κ° μ μμ μμ ν μ€νΈ
- μμ‘΄μ± νμΈ: μμμμ μ°Έμ‘°νλ λͺ¨λ μ μ΄ μ‘΄μ¬νλμ§ νμΈ
- μμ§ μΌμ΄μ€ ν μ€νΈ: 0, μμ, λ§€μ° ν° κ° ν¬ν¨
scripts/recalc.py μΆλ ₯ ν΄μ
μ€ν¬λ¦½νΈλ μ€λ₯ μΈλΆμ¬νμ΄ ν¬ν¨λ JSONμ λ°νν©λλ€:
{
"status": "success", // λλ "errors_found"
"total_errors": 0, // μ΄ μ€λ₯ μ
"total_formulas": 42, // νμΌ λ΄ μμ μ
"error_summary": { // μ€λ₯κ° λ°κ²¬λ κ²½μ°μλ§ μ‘΄μ¬
"#REF!": {
"count": 2,
"locations": ["Sheet1!B5", "Sheet1!C10"]
}
}
}
λͺ¨λ² μ¬λ‘
λΌμ΄λΈλ¬λ¦¬ μ ν
- pandas: λ°μ΄ν° λΆμ, λλ μμ , κ°λ¨ν λ°μ΄ν° λ΄λ³΄λ΄κΈ°μ μ΅μ
- openpyxl: 볡μ‘ν μμ, μμ, Excel μ μ© κΈ°λ₯μ μ΅μ
openpyxl μμ
- μ μΈλ±μ€λ 1λΆν° μμ (row=1, column=1μ A1 μ μ κ°λ¦¬ν΄)
- κ³μ°λ κ°μ μ½μΌλ €λ©΄
data_only=Trueμ¬μ©:load_workbook('file.xlsx', data_only=True) - κ²½κ³ :
data_only=Trueλ‘ μ΄κ³ μ μ₯νλ©΄ μμμ΄ κ°μΌλ‘ λ체λμ΄ μꡬμ μΌλ‘ μμ€λ©λλ€ - λμ©λ νμΌμ κ²½μ°: μ½κΈ°μ
read_only=True, μ°κΈ°μwrite_only=Trueμ¬μ© - μμμ μ μ§λμ§λ§ νκ°λμ§ μμ - κ° μ λ°μ΄νΈμ scripts/recalc.py μ¬μ©
pandas μμ
- μΆλ‘ λ¬Έμ λ₯Ό νΌνλ €λ©΄ λ°μ΄ν° νμ
μ μ§μ :
pd.read_excel('file.xlsx', dtype={'id': str}) - λμ©λ νμΌμ κ²½μ° νΉμ μ΄λ§ μ½κΈ°:
pd.read_excel('file.xlsx', usecols=['A', 'C', 'E']) - λ μ§λ₯Ό μ¬λ°λ₯΄κ² μ²λ¦¬:
pd.read_excel('file.xlsx', parse_dates=['date_column'])
μ½λ μ€νμΌ κ°μ΄λλΌμΈ
μ€μ: Excel μμ μ μν Python μ½λ μμ± μ:
- λΆνμν μ£Όμ μμ΄ μ΅μνμ κ°κ²°ν Python μ½λ μμ±
- μ₯ν©ν λ³μλͺ κ³Ό λΆνμν μμ νΌνκΈ°
- λΆνμν print λ¬Έ νΌνκΈ°
Excel νμΌ μ체μ λν΄:
- 볡μ‘ν μμμ΄λ μ€μν κ°μ μ΄ μλ μ μ μ½λ©νΈ μΆκ°
- νλμ½λ©λ κ°μ λ°μ΄ν° μΆμ² λ¬Έμν
- ν΅μ¬ κ³μ°κ³Ό λͺ¨λΈ μΉμ μ λν λ ΈνΈ ν¬ν¨